PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 3.100.2
MailPoet – Newsletters, Email Marketing, and Automation v3.100.2
5.28.1 5.28.0 5.27.0 5.26.0 5.26.1 5.25.0 5.24.0 4.43.0 4.43.1 4.44.0 4.44.1 4.45.0 4.46.0 4.47.0 4.48.0 4.48.1 4.48.2 4.49.0 4.49.1 4.5.0 4.5.1 4.5.2 4.50.0 4.50.1 4.51.0 4.51.1 4.51.2 4.52.0 4.53.0 4.54.0 4.55.0 4.56.0 4.57.0 4.58.0 4.58.1 4.58.2 4.6.0 4.6.1 4.6.2 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 5.0.0 5.0.1 5.0.2 5.1.0 5.1.1 5.10.0 5.10.1 5.11.0 5.12.0 5.12.1 5.12.10 5.12.11 5.12.12 5.12.13 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.12.7 5.12.8 5.12.9 5.13.0 5.13.1 5.13.2 5.14.0 5.14.1 5.14.2 5.14.3 5.15.0 5.15.1 5.16.0 5.16.1 5.16.2 5.16.3 5.16.4 5.17.0 5.17.1 5.17.2 5.17.3 5.17.4 5.17.5 5.17.6 5.18.0 5.19.0 5.2.0 5.2.1 5.2.2 5.2.3 5.20.0 5.21.0 5.21.1 5.21.2 5.21.3 5.22.0 5.22.1 5.22.2 5.22.3 5.22.4 5.23.0 5.23.1 5.23.2 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.4.0 5.4.1 5.4.2 5.5.0 5.5.1 5.5.2 5.6.0 5.6.1 5.6.2 5.6.3 5.6.4 5.7.0 5.7.1 5.8.0 5.8.1 5.9.0 3.0.0-beta.15 3.7.1 3.0.0-beta.16 3.7.2 3.0.0-beta.17 3.7.3 3.0.0-beta.18 3.7.4 3.0.0-beta.19 3.7.5 3.0.0-beta.2 3.7.6 3.0.0-beta.20 3.7.8 3.0.0-beta.21 3.70.0 3.0.0-beta.22 3.71.0 3.0.0-beta.23 3.71.1 3.0.0-beta.23.1 3.71.2 3.0.0-beta.23.2 3.71.3 3.0.0-beta.24 3.72.0 3.0.0-beta.25 3.73.0 3.0.0-beta.26 3.73.1 3.0.0-beta.27 3.73.2 3.0.0-beta.28 3.74.0 3.0.0-beta.29 3.74.1 3.0.0-beta.3 3.74.2 3.0.0-beta.30 3.74.3 3.0.0-beta.31 3.75.0 3.0.0-beta.32 3.75.1 3.0.0-beta.33 3.76.0 3.0.0-beta.33.1 3.77.0 3.0.0-beta.34.0.0 3.77.1 3.0.0-beta.36.0.0 3.78.0 3.0.0-beta.36.0.1 3.79.0 3.0.0-beta.36.2.0 3.8 3.0.0-beta.36.3.0 3.8.1 3.0.0-beta.36.3.1 3.8.2 3.0.0-beta.37.0.0 3.8.3 3.0.0-beta.4 3.8.4 3.0.0-beta.5 3.8.5 3.0.0-beta.6 3.8.6 3.0.0-beta.7 3.80.0 3.0.0-beta.7.1 3.81.0 3.0.0-beta.8 3.82.0 3.0.0-beta.9 3.83.0 3.0.0-rc.1.0.0 3.84.0 3.0.0-rc.1.0.1 3.84.1 3.0.0-rc.1.0.2 3.85.0 3.0.0-rc.1.0.3 3.85.1 3.0.0-rc.1.0.4 3.86.0 3.0.0-rc.2.0.0 3.87.0 3.0.0-rc.2.0.1 3.87.1 3.0.0-rc.2.0.2 3.87.2 3.0.0-rc.2.0.3 3.88.0 3.0.1 3.88.1 3.0.2 3.88.2 3.0.3 3.89.0 3.0.4 3.89.1 3.0.5 3.89.2 3.0.6 3.89.3 3.0.7 3.89.4 3.0.8 3.9.0 3.0.9 3.9.1 3.1.0 3.90.0 3.10 3.90.1 3.10.1 3.90.2 3.100.0 3.91.0 3.100.1 3.91.1 3.100.2 3.92.0 3.101.0 3.92.1 3.101.1 3.93.0 3.102.0 3.93.1 3.102.1 3.94.0 3.103.0 3.95.0 3.103.1 3.95.1 3.11.0 3.96.0 3.11.1 3.96.1 3.11.2 3.97.0 3.11.3 3.98.0 3.11.4 3.98.1 3.11.5 3.99.0 3.12.0 3.99.1 3.12.1 4.0.0 3.13.0 4.0.1 3.14.0 4.1.0 3.14.1 4.1.1 3.15.0 4.10.0 3.16.0 4.11.0 3.16.1 4.11.1 3.16.2 4.12.0 3.16.3 4.12.1 3.17.0 4.12.2 3.17.1 4.13.0 3.17.2 4.14.0 3.18.0 4.15.0 3.18.1 4.16.0 3.18.2 4.17.0 3.19.0 4.17.1 3.19.1 4.18.0 3.19.2 4.18.1 3.19.3 4.19.0 3.2.0 4.2.0 3.2.1 4.20.0 3.2.2 4.20.1 3.2.3 4.20.2 3.2.4 4.21.0 3.2.5 4.22.0 3.20.0 4.22.1 3.21.0 4.22.2 3.21.1 4.23.0 3.22.0 4.24.0 3.23.0 4.25.0 3.23.1 4.26.0 3.23.2 4.26.1 3.24.0 4.27.0 3.25.0 4.28.0 3.25.1 4.29.0 3.26.0 4.3.0 3.26.1 4.3.1 3.27.0 4.30.0 3.28.0 4.31.0 3.29.0 4.31.1 3.3.0 4.32.0 3.3.1 4.33.0 3.3.2 4.34.0 3.3.3 4.35.0 3.3.4 4.35.1 3.3.5 4.36.0 3.3.6 4.37.0 3.30.0 4.38.0 3.31.0 4.39.0 3.31.1 4.4.0 3.32.0 4.40.0 3.32.1 4.41.0 3.32.2 4.41.1 3.33.0 4.41.2 3.34.0 4.41.3 3.34.1 4.42.0 3.34.2 4.42.1 3.34.3 3.34.4 3.35.0 3.35.1 3.35.3 3.35.4 3.36.0 3.37.0 3.37.1 3.37.2 3.37.3 3.38.0 3.38.1 3.39.0 3.39.1 3.39.2 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.40.0 3.40.1 3.41.0 3.41.1 3.41.2 3.42.0 3.42.1 3.42.2 3.42.3 3.43.0 3.43.1 3.44.0 3.45.0 3.45.1 3.46.0 3.46.1 3.46.10 3.46.11 3.46.12 3.46.13 3.46.14 3.46.2 3.46.3 3.46.4 3.46.5 3.46.6 3.46.7 3.46.8 3.46.9 3.47.0 3.47.1 3.47.10 3.47.11 3.47.2 3.47.3 3.47.5 3.47.6 3.47.7 3.47.9 3.48.0 3.48.1 3.49.0 3.49.1 3.5.0 3.5.1 3.50.0 3.51.0 3.51.1 3.51.2 3.52.0 3.53.0 3.54.0 3.54.1 3.54.2 3.54.3 3.55.0 3.55.1 3.56.0 3.56.1 3.56.2 3.57.0 3.57.1 3.58.0 3.59.0 3.59.1 3.59.2 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.60.0 3.60.1 3.60.10 3.60.11 3.60.12 3.60.2 3.60.3 3.60.4 3.60.6 3.60.7 3.60.8 3.60.9 3.61.0 3.62.0 3.62.1 3.63.0 3.64.0 3.64.1 3.64.2 3.64.3 3.65.0 trunk 3.65.1 3.0.0 3.66.0 3.0.0-beta.1 3.67.0 3.0.0-beta.10 3.67.1 3.0.0-beta.11 3.68.0 3.0.0-beta.12 3.69.0 3.0.0-beta.13 3.69.1 3.0.0-beta.14 3.7.0
mailpoet / lib-3rd-party / pquery / third_party / jsminplus.php
mailpoet / lib-3rd-party / pquery / third_party Last commit date
index.php 4 years ago jsminplus.php 4 years ago
jsminplus.php
2092 lines
1 <?php
2
3 namespace MailPoetVendor\pQuery;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 /**
9 * JSMinPlus version 1.4
10 *
11 * Minifies a javascript file using a javascript parser
12 *
13 * This implements a PHP port of Brendan Eich's Narcissus open source javascript engine (in javascript)
14 * References: http://en.wikipedia.org/wiki/Narcissus_(JavaScript_engine)
15 * Narcissus sourcecode: http://mxr.mozilla.org/mozilla/source/js/narcissus/
16 * JSMinPlus weblog: http://crisp.tweakblogs.net/blog/cat/716
17 *
18 * Tino Zijdel <crisp@tweakers.net>
19 *
20 * Usage: $minified = JSMinPlus::minify($script [, $filename])
21 *
22 * Versionlog (see also changelog.txt):
23 * 23-07-2011 - remove dynamic creation of OP_* and KEYWORD_* defines and declare them on top
24 * reduce memory footprint by minifying by block-scope
25 * some small byte-saving and performance improvements
26 * 12-05-2009 - fixed hook:colon precedence, fixed empty body in loop and if-constructs
27 * 18-04-2009 - fixed crashbug in PHP 5.2.9 and several other bugfixes
28 * 12-04-2009 - some small bugfixes and performance improvements
29 * 09-04-2009 - initial open sourced version 1.0
30 *
31 * Latest version of this script: http://files.tweakers.net/jsminplus/jsminplus.zip
32 *
33 */
34
35 /* ***** BEGIN LICENSE BLOCK *****
36 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
37 *
38 * The contents of this file are subject to the Mozilla Public License Version
39 * 1.1 (the "License"); you may not use this file except in compliance with
40 * the License. You may obtain a copy of the License at
41 * http://www.mozilla.org/MPL/
42 *
43 * Software distributed under the License is distributed on an "AS IS" basis,
44 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
45 * for the specific language governing rights and limitations under the
46 * License.
47 *
48 * The Original Code is the Narcissus JavaScript engine.
49 *
50 * The Initial Developer of the Original Code is
51 * Brendan Eich <brendan@mozilla.org>.
52 * Portions created by the Initial Developer are Copyright (C) 2004
53 * the Initial Developer. All Rights Reserved.
54 *
55 * Contributor(s): Tino Zijdel <crisp@tweakers.net>
56 * PHP port, modifications and minifier routine are (C) 2009-2011
57 *
58 * Alternatively, the contents of this file may be used under the terms of
59 * either the GNU General Public License Version 2 or later (the "GPL"), or
60 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
61 * in which case the provisions of the GPL or the LGPL are applicable instead
62 * of those above. If you wish to allow use of your version of this file only
63 * under the terms of either the GPL or the LGPL, and not to allow others to
64 * use your version of this file under the terms of the MPL, indicate your
65 * decision by deleting the provisions above and replace them with the notice
66 * and other provisions required by the GPL or the LGPL. If you do not delete
67 * the provisions above, a recipient may use your version of this file under
68 * the terms of any one of the MPL, the GPL or the LGPL.
69 *
70 * ***** END LICENSE BLOCK ***** */
71
72 define('TOKEN_END', 1);
73 define('TOKEN_NUMBER', 2);
74 define('TOKEN_IDENTIFIER', 3);
75 define('TOKEN_STRING', 4);
76 define('TOKEN_REGEXP', 5);
77 define('TOKEN_NEWLINE', 6);
78 define('TOKEN_CONDCOMMENT_START', 7);
79 define('TOKEN_CONDCOMMENT_END', 8);
80
81 define('JS_SCRIPT', 100);
82 define('JS_BLOCK', 101);
83 define('JS_LABEL', 102);
84 define('JS_FOR_IN', 103);
85 define('JS_CALL', 104);
86 define('JS_NEW_WITH_ARGS', 105);
87 define('JS_INDEX', 106);
88 define('JS_ARRAY_INIT', 107);
89 define('JS_OBJECT_INIT', 108);
90 define('JS_PROPERTY_INIT', 109);
91 define('JS_GETTER', 110);
92 define('JS_SETTER', 111);
93 define('JS_GROUP', 112);
94 define('JS_LIST', 113);
95
96 define('JS_MINIFIED', 999);
97
98 define('DECLARED_FORM', 0);
99 define('EXPRESSED_FORM', 1);
100 define('STATEMENT_FORM', 2);
101
102 /* Operators */
103 define('OP_SEMICOLON', ';');
104 define('OP_COMMA', ',');
105 define('OP_HOOK', '?');
106 define('OP_COLON', ':');
107 define('OP_OR', '||');
108 define('OP_AND', '&&');
109 define('OP_BITWISE_OR', '|');
110 define('OP_BITWISE_XOR', '^');
111 define('OP_BITWISE_AND', '&');
112 define('OP_STRICT_EQ', '===');
113 define('OP_EQ', '==');
114 define('OP_ASSIGN', '=');
115 define('OP_STRICT_NE', '!==');
116 define('OP_NE', '!=');
117 define('OP_LSH', '<<');
118 define('OP_LE', '<=');
119 define('OP_LT', '<');
120 define('OP_URSH', '>>>');
121 define('OP_RSH', '>>');
122 define('OP_GE', '>=');
123 define('OP_GT', '>');
124 define('OP_INCREMENT', '++');
125 define('OP_DECREMENT', '--');
126 define('OP_PLUS', '+');
127 define('OP_MINUS', '-');
128 define('OP_MUL', '*');
129 define('OP_DIV', '/');
130 define('OP_MOD', '%');
131 define('OP_NOT', '!');
132 define('OP_BITWISE_NOT', '~');
133 define('OP_DOT', '.');
134 define('OP_LEFT_BRACKET', '[');
135 define('OP_RIGHT_BRACKET', ']');
136 define('OP_LEFT_CURLY', '{');
137 define('OP_RIGHT_CURLY', '}');
138 define('OP_LEFT_PAREN', '(');
139 define('OP_RIGHT_PAREN', ')');
140 define('OP_CONDCOMMENT_END', '@*/');
141
142 define('OP_UNARY_PLUS', 'U+');
143 define('OP_UNARY_MINUS', 'U-');
144
145 /* Keywords */
146 define('KEYWORD_BREAK', 'break');
147 define('KEYWORD_CASE', 'case');
148 define('KEYWORD_CATCH', 'catch');
149 define('KEYWORD_CONST', 'const');
150 define('KEYWORD_CONTINUE', 'continue');
151 define('KEYWORD_DEBUGGER', 'debugger');
152 define('KEYWORD_DEFAULT', 'default');
153 define('KEYWORD_DELETE', 'delete');
154 define('KEYWORD_DO', 'do');
155 define('KEYWORD_ELSE', 'else');
156 define('KEYWORD_ENUM', 'enum');
157 define('KEYWORD_FALSE', 'false');
158 define('KEYWORD_FINALLY', 'finally');
159 define('KEYWORD_FOR', 'for');
160 define('KEYWORD_FUNCTION', 'function');
161 define('KEYWORD_IF', 'if');
162 define('KEYWORD_IN', 'in');
163 define('KEYWORD_INSTANCEOF', 'instanceof');
164 define('KEYWORD_NEW', 'new');
165 define('KEYWORD_NULL', 'null');
166 define('KEYWORD_RETURN', 'return');
167 define('KEYWORD_SWITCH', 'switch');
168 define('KEYWORD_THIS', 'this');
169 define('KEYWORD_THROW', 'throw');
170 define('KEYWORD_TRUE', 'true');
171 define('KEYWORD_TRY', 'try');
172 define('KEYWORD_TYPEOF', 'typeof');
173 define('KEYWORD_VAR', 'var');
174 define('KEYWORD_VOID', 'void');
175 define('KEYWORD_WHILE', 'while');
176 define('KEYWORD_WITH', 'with');
177
178
179 class JSMinPlus
180 {
181 private $parser;
182 private $reserved = array(
183 'break', 'case', 'catch', 'continue', 'default', 'delete', 'do',
184 'else', 'finally', 'for', 'function', 'if', 'in', 'instanceof',
185 'new', 'return', 'switch', 'this', 'throw', 'try', 'typeof', 'var',
186 'void', 'while', 'with',
187 // Words reserved for future use
188 'abstract', 'boolean', 'byte', 'char', 'class', 'const', 'debugger',
189 'double', 'enum', 'export', 'extends', 'final', 'float', 'goto',
190 'implements', 'import', 'int', 'interface', 'long', 'native',
191 'package', 'private', 'protected', 'public', 'short', 'static',
192 'super', 'synchronized', 'throws', 'transient', 'volatile',
193 // These are not reserved, but should be taken into account
194 // in isValidIdentifier (See jslint source code)
195 'arguments', 'eval', 'true', 'false', 'Infinity', 'NaN', 'null', 'undefined'
196 );
197
198 private function __construct()
199 {
200 $this->parser = new JSParser($this);
201 }
202
203 public static function minify($js, $filename='')
204 {
205 static $instance;
206
207 // this is a singleton
208 if(!$instance)
209 $instance = new JSMinPlus();
210
211 return $instance->min($js, $filename);
212 }
213
214 private function min($js, $filename)
215 {
216 try
217 {
218 $n = $this->parser->parse($js, $filename, 1);
219 return $this->parseTree($n);
220 }
221 catch(Exception $e)
222 {
223 echo $e->getMessage() . "\n";
224 }
225
226 return false;
227 }
228
229 public function parseTree($n, $noBlockGrouping = false)
230 {
231 $s = '';
232
233 switch ($n->type)
234 {
235 case JS_MINIFIED:
236 $s = $n->value;
237 break;
238
239 case JS_SCRIPT:
240 // we do nothing yet with funDecls or varDecls
241 $noBlockGrouping = true;
242 // FALL THROUGH
243
244 case JS_BLOCK:
245 $childs = $n->treeNodes;
246 $lastType = 0;
247 for ($c = 0, $i = 0, $j = count($childs); $i < $j; $i++)
248 {
249 $type = $childs[$i]->type;
250 $t = $this->parseTree($childs[$i]);
251 if (strlen($t))
252 {
253 if ($c)
254 {
255 $s = rtrim($s, ';');
256
257 if ($type == KEYWORD_FUNCTION && $childs[$i]->functionForm == DECLARED_FORM)
258 {
259 // put declared functions on a new line
260 $s .= "\n";
261 }
262 elseif ($type == KEYWORD_VAR && $type == $lastType)
263 {
264 // mutiple var-statements can go into one
265 $t = ',' . substr($t, 4);
266 }
267 else
268 {
269 // add terminator
270 $s .= ';';
271 }
272 }
273
274 $s .= $t;
275
276 $c++;
277 $lastType = $type;
278 }
279 }
280
281 if ($c > 1 && !$noBlockGrouping)
282 {
283 $s = '{' . $s . '}';
284 }
285 break;
286
287 case KEYWORD_FUNCTION:
288 $s .= 'function' . ($n->name ? ' ' . $n->name : '') . '(';
289 $params = $n->params;
290 for ($i = 0, $j = count($params); $i < $j; $i++)
291 $s .= ($i ? ',' : '') . $params[$i];
292 $s .= '){' . $this->parseTree($n->body, true) . '}';
293 break;
294
295 case KEYWORD_IF:
296 $s = 'if(' . $this->parseTree($n->condition) . ')';
297 $thenPart = $this->parseTree($n->thenPart);
298 $elsePart = $n->elsePart ? $this->parseTree($n->elsePart) : null;
299
300 // empty if-statement
301 if ($thenPart == '')
302 $thenPart = ';';
303
304 if ($elsePart)
305 {
306 // be carefull and always make a block out of the thenPart; could be more optimized but is a lot of trouble
307 if ($thenPart != ';' && $thenPart[0] != '{')
308 $thenPart = '{' . $thenPart . '}';
309
310 $s .= $thenPart . 'else';
311
312 // we could check for more, but that hardly ever applies so go for performance
313 if ($elsePart[0] != '{')
314 $s .= ' ';
315
316 $s .= $elsePart;
317 }
318 else
319 {
320 $s .= $thenPart;
321 }
322 break;
323
324 case KEYWORD_SWITCH:
325 $s = 'switch(' . $this->parseTree($n->discriminant) . '){';
326 $cases = $n->cases;
327 for ($i = 0, $j = count($cases); $i < $j; $i++)
328 {
329 $case = $cases[$i];
330 if ($case->type == KEYWORD_CASE)
331 $s .= 'case' . ($case->caseLabel->type != TOKEN_STRING ? ' ' : '') . $this->parseTree($case->caseLabel) . ':';
332 else
333 $s .= 'default:';
334
335 $statement = $this->parseTree($case->statements, true);
336 if ($statement)
337 {
338 $s .= $statement;
339 // no terminator for last statement
340 if ($i + 1 < $j)
341 $s .= ';';
342 }
343 }
344 $s .= '}';
345 break;
346
347 case KEYWORD_FOR:
348 $s = 'for(' . ($n->setup ? $this->parseTree($n->setup) : '')
349 . ';' . ($n->condition ? $this->parseTree($n->condition) : '')
350 . ';' . ($n->update ? $this->parseTree($n->update) : '') . ')';
351
352 $body = $this->parseTree($n->body);
353 if ($body == '')
354 $body = ';';
355
356 $s .= $body;
357 break;
358
359 case KEYWORD_WHILE:
360 $s = 'while(' . $this->parseTree($n->condition) . ')';
361
362 $body = $this->parseTree($n->body);
363 if ($body == '')
364 $body = ';';
365
366 $s .= $body;
367 break;
368
369 case JS_FOR_IN:
370 $s = 'for(' . ($n->varDecl ? $this->parseTree($n->varDecl) : $this->parseTree($n->iterator)) . ' in ' . $this->parseTree($n->object) . ')';
371
372 $body = $this->parseTree($n->body);
373 if ($body == '')
374 $body = ';';
375
376 $s .= $body;
377 break;
378
379 case KEYWORD_DO:
380 $s = 'do{' . $this->parseTree($n->body, true) . '}while(' . $this->parseTree($n->condition) . ')';
381 break;
382
383 case KEYWORD_BREAK:
384 case KEYWORD_CONTINUE:
385 $s = $n->value . ($n->label ? ' ' . $n->label : '');
386 break;
387
388 case KEYWORD_TRY:
389 $s = 'try{' . $this->parseTree($n->tryBlock, true) . '}';
390 $catchClauses = $n->catchClauses;
391 for ($i = 0, $j = count($catchClauses); $i < $j; $i++)
392 {
393 $t = $catchClauses[$i];
394 $s .= 'catch(' . $t->varName . ($t->guard ? ' if ' . $this->parseTree($t->guard) : '') . '){' . $this->parseTree($t->block, true) . '}';
395 }
396 if ($n->finallyBlock)
397 $s .= 'finally{' . $this->parseTree($n->finallyBlock, true) . '}';
398 break;
399
400 case KEYWORD_THROW:
401 case KEYWORD_RETURN:
402 $s = $n->type;
403 if ($n->value)
404 {
405 $t = $this->parseTree($n->value);
406 if (strlen($t))
407 {
408 if ($this->isWordChar($t[0]) || $t[0] == '\\')
409 $s .= ' ';
410
411 $s .= $t;
412 }
413 }
414 break;
415
416 case KEYWORD_WITH:
417 $s = 'with(' . $this->parseTree($n->object) . ')' . $this->parseTree($n->body);
418 break;
419
420 case KEYWORD_VAR:
421 case KEYWORD_CONST:
422 $s = $n->value . ' ';
423 $childs = $n->treeNodes;
424 for ($i = 0, $j = count($childs); $i < $j; $i++)
425 {
426 $t = $childs[$i];
427 $s .= ($i ? ',' : '') . $t->name;
428 $u = $t->initializer;
429 if ($u)
430 $s .= '=' . $this->parseTree($u);
431 }
432 break;
433
434 case KEYWORD_IN:
435 case KEYWORD_INSTANCEOF:
436 $left = $this->parseTree($n->treeNodes[0]);
437 $right = $this->parseTree($n->treeNodes[1]);
438
439 $s = $left;
440
441 if ($this->isWordChar(substr($left, -1)))
442 $s .= ' ';
443
444 $s .= $n->type;
445
446 if ($this->isWordChar($right[0]) || $right[0] == '\\')
447 $s .= ' ';
448
449 $s .= $right;
450 break;
451
452 case KEYWORD_DELETE:
453 case KEYWORD_TYPEOF:
454 $right = $this->parseTree($n->treeNodes[0]);
455
456 $s = $n->type;
457
458 if ($this->isWordChar($right[0]) || $right[0] == '\\')
459 $s .= ' ';
460
461 $s .= $right;
462 break;
463
464 case KEYWORD_VOID:
465 $s = 'void(' . $this->parseTree($n->treeNodes[0]) . ')';
466 break;
467
468 case KEYWORD_DEBUGGER:
469 throw new Exception('NOT IMPLEMENTED: DEBUGGER');
470 break;
471
472 case TOKEN_CONDCOMMENT_START:
473 case TOKEN_CONDCOMMENT_END:
474 $s = $n->value . ($n->type == TOKEN_CONDCOMMENT_START ? ' ' : '');
475 $childs = $n->treeNodes;
476 for ($i = 0, $j = count($childs); $i < $j; $i++)
477 $s .= $this->parseTree($childs[$i]);
478 break;
479
480 case OP_SEMICOLON:
481 if ($expression = $n->expression)
482 $s = $this->parseTree($expression);
483 break;
484
485 case JS_LABEL:
486 $s = $n->label . ':' . $this->parseTree($n->statement);
487 break;
488
489 case OP_COMMA:
490 $childs = $n->treeNodes;
491 for ($i = 0, $j = count($childs); $i < $j; $i++)
492 $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]);
493 break;
494
495 case OP_ASSIGN:
496 $s = $this->parseTree($n->treeNodes[0]) . $n->value . $this->parseTree($n->treeNodes[1]);
497 break;
498
499 case OP_HOOK:
500 $s = $this->parseTree($n->treeNodes[0]) . '?' . $this->parseTree($n->treeNodes[1]) . ':' . $this->parseTree($n->treeNodes[2]);
501 break;
502
503 case OP_OR: case OP_AND:
504 case OP_BITWISE_OR: case OP_BITWISE_XOR: case OP_BITWISE_AND:
505 case OP_EQ: case OP_NE: case OP_STRICT_EQ: case OP_STRICT_NE:
506 case OP_LT: case OP_LE: case OP_GE: case OP_GT:
507 case OP_LSH: case OP_RSH: case OP_URSH:
508 case OP_MUL: case OP_DIV: case OP_MOD:
509 $s = $this->parseTree($n->treeNodes[0]) . $n->type . $this->parseTree($n->treeNodes[1]);
510 break;
511
512 case OP_PLUS:
513 case OP_MINUS:
514 $left = $this->parseTree($n->treeNodes[0]);
515 $right = $this->parseTree($n->treeNodes[1]);
516
517 switch ($n->treeNodes[1]->type)
518 {
519 case OP_PLUS:
520 case OP_MINUS:
521 case OP_INCREMENT:
522 case OP_DECREMENT:
523 case OP_UNARY_PLUS:
524 case OP_UNARY_MINUS:
525 $s = $left . $n->type . ' ' . $right;
526 break;
527
528 case TOKEN_STRING:
529 //combine concatted strings with same quotestyle
530 if ($n->type == OP_PLUS && substr($left, -1) == $right[0])
531 {
532 $s = substr($left, 0, -1) . substr($right, 1);
533 break;
534 }
535 // FALL THROUGH
536
537 default:
538 $s = $left . $n->type . $right;
539 }
540 break;
541
542 case OP_NOT:
543 case OP_BITWISE_NOT:
544 case OP_UNARY_PLUS:
545 case OP_UNARY_MINUS:
546 $s = $n->value . $this->parseTree($n->treeNodes[0]);
547 break;
548
549 case OP_INCREMENT:
550 case OP_DECREMENT:
551 if ($n->postfix)
552 $s = $this->parseTree($n->treeNodes[0]) . $n->value;
553 else
554 $s = $n->value . $this->parseTree($n->treeNodes[0]);
555 break;
556
557 case OP_DOT:
558 $s = $this->parseTree($n->treeNodes[0]) . '.' . $this->parseTree($n->treeNodes[1]);
559 break;
560
561 case JS_INDEX:
562 $s = $this->parseTree($n->treeNodes[0]);
563 // See if we can replace named index with a dot saving 3 bytes
564 if ( $n->treeNodes[0]->type == TOKEN_IDENTIFIER &&
565 $n->treeNodes[1]->type == TOKEN_STRING &&
566 $this->isValidIdentifier(substr($n->treeNodes[1]->value, 1, -1))
567 )
568 $s .= '.' . substr($n->treeNodes[1]->value, 1, -1);
569 else
570 $s .= '[' . $this->parseTree($n->treeNodes[1]) . ']';
571 break;
572
573 case JS_LIST:
574 $childs = $n->treeNodes;
575 for ($i = 0, $j = count($childs); $i < $j; $i++)
576 $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]);
577 break;
578
579 case JS_CALL:
580 $s = $this->parseTree($n->treeNodes[0]) . '(' . $this->parseTree($n->treeNodes[1]) . ')';
581 break;
582
583 case KEYWORD_NEW:
584 case JS_NEW_WITH_ARGS:
585 $s = 'new ' . $this->parseTree($n->treeNodes[0]) . '(' . ($n->type == JS_NEW_WITH_ARGS ? $this->parseTree($n->treeNodes[1]) : '') . ')';
586 break;
587
588 case JS_ARRAY_INIT:
589 $s = '[';
590 $childs = $n->treeNodes;
591 for ($i = 0, $j = count($childs); $i < $j; $i++)
592 {
593 $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]);
594 }
595 $s .= ']';
596 break;
597
598 case JS_OBJECT_INIT:
599 $s = '{';
600 $childs = $n->treeNodes;
601 for ($i = 0, $j = count($childs); $i < $j; $i++)
602 {
603 $t = $childs[$i];
604 if ($i)
605 $s .= ',';
606 if ($t->type == JS_PROPERTY_INIT)
607 {
608 // Ditch the quotes when the index is a valid identifier
609 if ( $t->treeNodes[0]->type == TOKEN_STRING &&
610 $this->isValidIdentifier(substr($t->treeNodes[0]->value, 1, -1))
611 )
612 $s .= substr($t->treeNodes[0]->value, 1, -1);
613 else
614 $s .= $t->treeNodes[0]->value;
615
616 $s .= ':' . $this->parseTree($t->treeNodes[1]);
617 }
618 else
619 {
620 $s .= $t->type == JS_GETTER ? 'get' : 'set';
621 $s .= ' ' . $t->name . '(';
622 $params = $t->params;
623 for ($i = 0, $j = count($params); $i < $j; $i++)
624 $s .= ($i ? ',' : '') . $params[$i];
625 $s .= '){' . $this->parseTree($t->body, true) . '}';
626 }
627 }
628 $s .= '}';
629 break;
630
631 case TOKEN_NUMBER:
632 $s = $n->value;
633 if (preg_match('/^([1-9]+)(0{3,})$/', $s, $m))
634 $s = $m[1] . 'e' . strlen($m[2]);
635 break;
636
637 case KEYWORD_NULL: case KEYWORD_THIS: case KEYWORD_TRUE: case KEYWORD_FALSE:
638 case TOKEN_IDENTIFIER: case TOKEN_STRING: case TOKEN_REGEXP:
639 $s = $n->value;
640 break;
641
642 case JS_GROUP:
643 if (in_array(
644 $n->treeNodes[0]->type,
645 array(
646 JS_ARRAY_INIT, JS_OBJECT_INIT, JS_GROUP,
647 TOKEN_NUMBER, TOKEN_STRING, TOKEN_REGEXP, TOKEN_IDENTIFIER,
648 KEYWORD_NULL, KEYWORD_THIS, KEYWORD_TRUE, KEYWORD_FALSE
649 )
650 ))
651 {
652 $s = $this->parseTree($n->treeNodes[0]);
653 }
654 else
655 {
656 $s = '(' . $this->parseTree($n->treeNodes[0]) . ')';
657 }
658 break;
659
660 default:
661 throw new Exception('UNKNOWN TOKEN TYPE: ' . $n->type);
662 }
663
664 return $s;
665 }
666
667 private function isValidIdentifier($string)
668 {
669 return preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $string) && !in_array($string, $this->reserved);
670 }
671
672 private function isWordChar($char)
673 {
674 return $char == '_' || $char == '$' || ctype_alnum($char);
675 }
676 }
677
678 class JSParser
679 {
680 private $t;
681 private $minifier;
682
683 private $opPrecedence = array(
684 ';' => 0,
685 ',' => 1,
686 '=' => 2, '?' => 2, ':' => 2,
687 // The above all have to have the same precedence, see bug 330975
688 '||' => 4,
689 '&&' => 5,
690 '|' => 6,
691 '^' => 7,
692 '&' => 8,
693 '==' => 9, '!=' => 9, '===' => 9, '!==' => 9,
694 '<' => 10, '<=' => 10, '>=' => 10, '>' => 10, 'in' => 10, 'instanceof' => 10,
695 '<<' => 11, '>>' => 11, '>>>' => 11,
696 '+' => 12, '-' => 12,
697 '*' => 13, '/' => 13, '%' => 13,
698 'delete' => 14, 'void' => 14, 'typeof' => 14,
699 '!' => 14, '~' => 14, 'U+' => 14, 'U-' => 14,
700 '++' => 15, '--' => 15,
701 'new' => 16,
702 '.' => 17,
703 JS_NEW_WITH_ARGS => 0, JS_INDEX => 0, JS_CALL => 0,
704 JS_ARRAY_INIT => 0, JS_OBJECT_INIT => 0, JS_GROUP => 0
705 );
706
707 private $opArity = array(
708 ',' => -2,
709 '=' => 2,
710 '?' => 3,
711 '||' => 2,
712 '&&' => 2,
713 '|' => 2,
714 '^' => 2,
715 '&' => 2,
716 '==' => 2, '!=' => 2, '===' => 2, '!==' => 2,
717 '<' => 2, '<=' => 2, '>=' => 2, '>' => 2, 'in' => 2, 'instanceof' => 2,
718 '<<' => 2, '>>' => 2, '>>>' => 2,
719 '+' => 2, '-' => 2,
720 '*' => 2, '/' => 2, '%' => 2,
721 'delete' => 1, 'void' => 1, 'typeof' => 1,
722 '!' => 1, '~' => 1, 'U+' => 1, 'U-' => 1,
723 '++' => 1, '--' => 1,
724 'new' => 1,
725 '.' => 2,
726 JS_NEW_WITH_ARGS => 2, JS_INDEX => 2, JS_CALL => 2,
727 JS_ARRAY_INIT => 1, JS_OBJECT_INIT => 1, JS_GROUP => 1,
728 TOKEN_CONDCOMMENT_START => 1, TOKEN_CONDCOMMENT_END => 1
729 );
730
731 public function __construct($minifier=null)
732 {
733 $this->minifier = $minifier;
734 $this->t = new JSTokenizer();
735 }
736
737 public function parse($s, $f, $l)
738 {
739 // initialize tokenizer
740 $this->t->init($s, $f, $l);
741
742 $x = new JSCompilerContext(false);
743 $n = $this->Script($x);
744 if (!$this->t->isDone())
745 throw $this->t->newSyntaxError('Syntax error');
746
747 return $n;
748 }
749
750 private function Script($x)
751 {
752 $n = $this->Statements($x);
753 $n->type = JS_SCRIPT;
754 $n->funDecls = $x->funDecls;
755 $n->varDecls = $x->varDecls;
756
757 // minify by scope
758 if ($this->minifier)
759 {
760 $n->value = $this->minifier->parseTree($n);
761
762 // clear tree from node to save memory
763 $n->treeNodes = null;
764 $n->funDecls = null;
765 $n->varDecls = null;
766
767 $n->type = JS_MINIFIED;
768 }
769
770 return $n;
771 }
772
773 private function Statements($x)
774 {
775 $n = new JSNode($this->t, JS_BLOCK);
776 array_push($x->stmtStack, $n);
777
778 while (!$this->t->isDone() && $this->t->peek() != OP_RIGHT_CURLY)
779 $n->addNode($this->Statement($x));
780
781 array_pop($x->stmtStack);
782
783 return $n;
784 }
785
786 private function Block($x)
787 {
788 $this->t->mustMatch(OP_LEFT_CURLY);
789 $n = $this->Statements($x);
790 $this->t->mustMatch(OP_RIGHT_CURLY);
791
792 return $n;
793 }
794
795 private function Statement($x)
796 {
797 $tt = $this->t->get();
798 $n2 = null;
799
800 // Cases for statements ending in a right curly return early, avoiding the
801 // common semicolon insertion magic after this switch.
802 switch ($tt)
803 {
804 case KEYWORD_FUNCTION:
805 return $this->FunctionDefinition(
806 $x,
807 true,
808 count($x->stmtStack) > 1 ? STATEMENT_FORM : DECLARED_FORM
809 );
810 break;
811
812 case OP_LEFT_CURLY:
813 $n = $this->Statements($x);
814 $this->t->mustMatch(OP_RIGHT_CURLY);
815 return $n;
816
817 case KEYWORD_IF:
818 $n = new JSNode($this->t);
819 $n->condition = $this->ParenExpression($x);
820 array_push($x->stmtStack, $n);
821 $n->thenPart = $this->Statement($x);
822 $n->elsePart = $this->t->match(KEYWORD_ELSE) ? $this->Statement($x) : null;
823 array_pop($x->stmtStack);
824 return $n;
825
826 case KEYWORD_SWITCH:
827 $n = new JSNode($this->t);
828 $this->t->mustMatch(OP_LEFT_PAREN);
829 $n->discriminant = $this->Expression($x);
830 $this->t->mustMatch(OP_RIGHT_PAREN);
831 $n->cases = array();
832 $n->defaultIndex = -1;
833
834 array_push($x->stmtStack, $n);
835
836 $this->t->mustMatch(OP_LEFT_CURLY);
837
838 while (($tt = $this->t->get()) != OP_RIGHT_CURLY)
839 {
840 switch ($tt)
841 {
842 case KEYWORD_DEFAULT:
843 if ($n->defaultIndex >= 0)
844 throw $this->t->newSyntaxError('More than one switch default');
845 // FALL THROUGH
846 case KEYWORD_CASE:
847 $n2 = new JSNode($this->t);
848 if ($tt == KEYWORD_DEFAULT)
849 $n->defaultIndex = count($n->cases);
850 else
851 $n2->caseLabel = $this->Expression($x, OP_COLON);
852 break;
853 default:
854 throw $this->t->newSyntaxError('Invalid switch case');
855 }
856
857 $this->t->mustMatch(OP_COLON);
858 $n2->statements = new JSNode($this->t, JS_BLOCK);
859 while (($tt = $this->t->peek()) != KEYWORD_CASE && $tt != KEYWORD_DEFAULT && $tt != OP_RIGHT_CURLY)
860 $n2->statements->addNode($this->Statement($x));
861
862 array_push($n->cases, $n2);
863 }
864
865 array_pop($x->stmtStack);
866 return $n;
867
868 case KEYWORD_FOR:
869 $n = new JSNode($this->t);
870 $n->isLoop = true;
871 $this->t->mustMatch(OP_LEFT_PAREN);
872
873 if (($tt = $this->t->peek()) != OP_SEMICOLON)
874 {
875 $x->inForLoopInit = true;
876 if ($tt == KEYWORD_VAR || $tt == KEYWORD_CONST)
877 {
878 $this->t->get();
879 $n2 = $this->Variables($x);
880 }
881 else
882 {
883 $n2 = $this->Expression($x);
884 }
885 $x->inForLoopInit = false;
886 }
887
888 if ($n2 && $this->t->match(KEYWORD_IN))
889 {
890 $n->type = JS_FOR_IN;
891 if ($n2->type == KEYWORD_VAR)
892 {
893 if (count($n2->treeNodes) != 1)
894 {
895 throw $this->t->newSyntaxError(
896 'Invalid for..in left-hand side',
897 $this->t->filename,
898 $n2->lineno
899 );
900 }
901
902 // NB: n2[0].type == IDENTIFIER and n2[0].value == n2[0].name.
903 $n->iterator = $n2->treeNodes[0];
904 $n->varDecl = $n2;
905 }
906 else
907 {
908 $n->iterator = $n2;
909 $n->varDecl = null;
910 }
911
912 $n->object = $this->Expression($x);
913 }
914 else
915 {
916 $n->setup = $n2 ? $n2 : null;
917 $this->t->mustMatch(OP_SEMICOLON);
918 $n->condition = $this->t->peek() == OP_SEMICOLON ? null : $this->Expression($x);
919 $this->t->mustMatch(OP_SEMICOLON);
920 $n->update = $this->t->peek() == OP_RIGHT_PAREN ? null : $this->Expression($x);
921 }
922
923 $this->t->mustMatch(OP_RIGHT_PAREN);
924 $n->body = $this->nest($x, $n);
925 return $n;
926
927 case KEYWORD_WHILE:
928 $n = new JSNode($this->t);
929 $n->isLoop = true;
930 $n->condition = $this->ParenExpression($x);
931 $n->body = $this->nest($x, $n);
932 return $n;
933
934 case KEYWORD_DO:
935 $n = new JSNode($this->t);
936 $n->isLoop = true;
937 $n->body = $this->nest($x, $n, KEYWORD_WHILE);
938 $n->condition = $this->ParenExpression($x);
939 if (!$x->ecmaStrictMode)
940 {
941 // <script language="JavaScript"> (without version hints) may need
942 // automatic semicolon insertion without a newline after do-while.
943 // See http://bugzilla.mozilla.org/show_bug.cgi?id=238945.
944 $this->t->match(OP_SEMICOLON);
945 return $n;
946 }
947 break;
948
949 case KEYWORD_BREAK:
950 case KEYWORD_CONTINUE:
951 $n = new JSNode($this->t);
952
953 if ($this->t->peekOnSameLine() == TOKEN_IDENTIFIER)
954 {
955 $this->t->get();
956 $n->label = $this->t->currentToken()->value;
957 }
958
959 $ss = $x->stmtStack;
960 $i = count($ss);
961 $label = $n->label;
962 if ($label)
963 {
964 do
965 {
966 if (--$i < 0)
967 throw $this->t->newSyntaxError('Label not found');
968 }
969 while ($ss[$i]->label != $label);
970 }
971 else
972 {
973 do
974 {
975 if (--$i < 0)
976 throw $this->t->newSyntaxError('Invalid ' . $tt);
977 }
978 while (!$ss[$i]->isLoop && ($tt != KEYWORD_BREAK || $ss[$i]->type != KEYWORD_SWITCH));
979 }
980
981 $n->target = $ss[$i];
982 break;
983
984 case KEYWORD_TRY:
985 $n = new JSNode($this->t);
986 $n->tryBlock = $this->Block($x);
987 $n->catchClauses = array();
988
989 while ($this->t->match(KEYWORD_CATCH))
990 {
991 $n2 = new JSNode($this->t);
992 $this->t->mustMatch(OP_LEFT_PAREN);
993 $n2->varName = $this->t->mustMatch(TOKEN_IDENTIFIER)->value;
994
995 if ($this->t->match(KEYWORD_IF))
996 {
997 if ($x->ecmaStrictMode)
998 throw $this->t->newSyntaxError('Illegal catch guard');
999
1000 if (count($n->catchClauses) && !end($n->catchClauses)->guard)
1001 throw $this->t->newSyntaxError('Guarded catch after unguarded');
1002
1003 $n2->guard = $this->Expression($x);
1004 }
1005 else
1006 {
1007 $n2->guard = null;
1008 }
1009
1010 $this->t->mustMatch(OP_RIGHT_PAREN);
1011 $n2->block = $this->Block($x);
1012 array_push($n->catchClauses, $n2);
1013 }
1014
1015 if ($this->t->match(KEYWORD_FINALLY))
1016 $n->finallyBlock = $this->Block($x);
1017
1018 if (!count($n->catchClauses) && !$n->finallyBlock)
1019 throw $this->t->newSyntaxError('Invalid try statement');
1020 return $n;
1021
1022 case KEYWORD_CATCH:
1023 case KEYWORD_FINALLY:
1024 throw $this->t->newSyntaxError($tt + ' without preceding try');
1025
1026 case KEYWORD_THROW:
1027 $n = new JSNode($this->t);
1028 $n->value = $this->Expression($x);
1029 break;
1030
1031 case KEYWORD_RETURN:
1032 if (!$x->inFunction)
1033 throw $this->t->newSyntaxError('Invalid return');
1034
1035 $n = new JSNode($this->t);
1036 $tt = $this->t->peekOnSameLine();
1037 if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY)
1038 $n->value = $this->Expression($x);
1039 else
1040 $n->value = null;
1041 break;
1042
1043 case KEYWORD_WITH:
1044 $n = new JSNode($this->t);
1045 $n->object = $this->ParenExpression($x);
1046 $n->body = $this->nest($x, $n);
1047 return $n;
1048
1049 case KEYWORD_VAR:
1050 case KEYWORD_CONST:
1051 $n = $this->Variables($x);
1052 break;
1053
1054 case TOKEN_CONDCOMMENT_START:
1055 case TOKEN_CONDCOMMENT_END:
1056 $n = new JSNode($this->t);
1057 return $n;
1058
1059 case KEYWORD_DEBUGGER:
1060 $n = new JSNode($this->t);
1061 break;
1062
1063 case TOKEN_NEWLINE:
1064 case OP_SEMICOLON:
1065 $n = new JSNode($this->t, OP_SEMICOLON);
1066 $n->expression = null;
1067 return $n;
1068
1069 default:
1070 if ($tt == TOKEN_IDENTIFIER)
1071 {
1072 $this->t->scanOperand = false;
1073 $tt = $this->t->peek();
1074 $this->t->scanOperand = true;
1075 if ($tt == OP_COLON)
1076 {
1077 $label = $this->t->currentToken()->value;
1078 $ss = $x->stmtStack;
1079 for ($i = count($ss) - 1; $i >= 0; --$i)
1080 {
1081 if ($ss[$i]->label == $label)
1082 throw $this->t->newSyntaxError('Duplicate label');
1083 }
1084
1085 $this->t->get();
1086 $n = new JSNode($this->t, JS_LABEL);
1087 $n->label = $label;
1088 $n->statement = $this->nest($x, $n);
1089
1090 return $n;
1091 }
1092 }
1093
1094 $n = new JSNode($this->t, OP_SEMICOLON);
1095 $this->t->unget();
1096 $n->expression = $this->Expression($x);
1097 $n->end = $n->expression->end;
1098 break;
1099 }
1100
1101 if ($this->t->lineno == $this->t->currentToken()->lineno)
1102 {
1103 $tt = $this->t->peekOnSameLine();
1104 if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY)
1105 throw $this->t->newSyntaxError('Missing ; before statement');
1106 }
1107
1108 $this->t->match(OP_SEMICOLON);
1109
1110 return $n;
1111 }
1112
1113 private function FunctionDefinition($x, $requireName, $functionForm)
1114 {
1115 $f = new JSNode($this->t);
1116
1117 if ($f->type != KEYWORD_FUNCTION)
1118 $f->type = ($f->value == 'get') ? JS_GETTER : JS_SETTER;
1119
1120 if ($this->t->match(TOKEN_IDENTIFIER))
1121 $f->name = $this->t->currentToken()->value;
1122 elseif ($requireName)
1123 throw $this->t->newSyntaxError('Missing function identifier');
1124
1125 $this->t->mustMatch(OP_LEFT_PAREN);
1126 $f->params = array();
1127
1128 while (($tt = $this->t->get()) != OP_RIGHT_PAREN)
1129 {
1130 if ($tt != TOKEN_IDENTIFIER)
1131 throw $this->t->newSyntaxError('Missing formal parameter');
1132
1133 array_push($f->params, $this->t->currentToken()->value);
1134
1135 if ($this->t->peek() != OP_RIGHT_PAREN)
1136 $this->t->mustMatch(OP_COMMA);
1137 }
1138
1139 $this->t->mustMatch(OP_LEFT_CURLY);
1140
1141 $x2 = new JSCompilerContext(true);
1142 $f->body = $this->Script($x2);
1143
1144 $this->t->mustMatch(OP_RIGHT_CURLY);
1145 $f->end = $this->t->currentToken()->end;
1146
1147 $f->functionForm = $functionForm;
1148 if ($functionForm == DECLARED_FORM)
1149 array_push($x->funDecls, $f);
1150
1151 return $f;
1152 }
1153
1154 private function Variables($x)
1155 {
1156 $n = new JSNode($this->t);
1157
1158 do
1159 {
1160 $this->t->mustMatch(TOKEN_IDENTIFIER);
1161
1162 $n2 = new JSNode($this->t);
1163 $n2->name = $n2->value;
1164
1165 if ($this->t->match(OP_ASSIGN))
1166 {
1167 if ($this->t->currentToken()->assignOp)
1168 throw $this->t->newSyntaxError('Invalid variable initialization');
1169
1170 $n2->initializer = $this->Expression($x, OP_COMMA);
1171 }
1172
1173 $n2->readOnly = $n->type == KEYWORD_CONST;
1174
1175 $n->addNode($n2);
1176 array_push($x->varDecls, $n2);
1177 }
1178 while ($this->t->match(OP_COMMA));
1179
1180 return $n;
1181 }
1182
1183 private function Expression($x, $stop=false)
1184 {
1185 $operators = array();
1186 $operands = array();
1187 $n = false;
1188
1189 $bl = $x->bracketLevel;
1190 $cl = $x->curlyLevel;
1191 $pl = $x->parenLevel;
1192 $hl = $x->hookLevel;
1193
1194 while (($tt = $this->t->get()) != TOKEN_END)
1195 {
1196 if ($tt == $stop &&
1197 $x->bracketLevel == $bl &&
1198 $x->curlyLevel == $cl &&
1199 $x->parenLevel == $pl &&
1200 $x->hookLevel == $hl
1201 )
1202 {
1203 // Stop only if tt matches the optional stop parameter, and that
1204 // token is not quoted by some kind of bracket.
1205 break;
1206 }
1207
1208 switch ($tt)
1209 {
1210 case OP_SEMICOLON:
1211 // NB: cannot be empty, Statement handled that.
1212 break 2;
1213
1214 case OP_HOOK:
1215 if ($this->t->scanOperand)
1216 break 2;
1217
1218 while ( !empty($operators) &&
1219 $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]
1220 )
1221 $this->reduce($operators, $operands);
1222
1223 array_push($operators, new JSNode($this->t));
1224
1225 ++$x->hookLevel;
1226 $this->t->scanOperand = true;
1227 $n = $this->Expression($x);
1228
1229 if (!$this->t->match(OP_COLON))
1230 break 2;
1231
1232 --$x->hookLevel;
1233 array_push($operands, $n);
1234 break;
1235
1236 case OP_COLON:
1237 if ($x->hookLevel)
1238 break 2;
1239
1240 throw $this->t->newSyntaxError('Invalid label');
1241 break;
1242
1243 case OP_ASSIGN:
1244 if ($this->t->scanOperand)
1245 break 2;
1246
1247 // Use >, not >=, for right-associative ASSIGN
1248 while ( !empty($operators) &&
1249 $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]
1250 )
1251 $this->reduce($operators, $operands);
1252
1253 array_push($operators, new JSNode($this->t));
1254 end($operands)->assignOp = $this->t->currentToken()->assignOp;
1255 $this->t->scanOperand = true;
1256 break;
1257
1258 case KEYWORD_IN:
1259 // An in operator should not be parsed if we're parsing the head of
1260 // a for (...) loop, unless it is in the then part of a conditional
1261 // expression, or parenthesized somehow.
1262 if ($x->inForLoopInit && !$x->hookLevel &&
1263 !$x->bracketLevel && !$x->curlyLevel &&
1264 !$x->parenLevel
1265 )
1266 break 2;
1267 // FALL THROUGH
1268 case OP_COMMA:
1269 // A comma operator should not be parsed if we're parsing the then part
1270 // of a conditional expression unless it's parenthesized somehow.
1271 if ($tt == OP_COMMA && $x->hookLevel &&
1272 !$x->bracketLevel && !$x->curlyLevel &&
1273 !$x->parenLevel
1274 )
1275 break 2;
1276 // Treat comma as left-associative so reduce can fold left-heavy
1277 // COMMA trees into a single array.
1278 // FALL THROUGH
1279 case OP_OR:
1280 case OP_AND:
1281 case OP_BITWISE_OR:
1282 case OP_BITWISE_XOR:
1283 case OP_BITWISE_AND:
1284 case OP_EQ: case OP_NE: case OP_STRICT_EQ: case OP_STRICT_NE:
1285 case OP_LT: case OP_LE: case OP_GE: case OP_GT:
1286 case KEYWORD_INSTANCEOF:
1287 case OP_LSH: case OP_RSH: case OP_URSH:
1288 case OP_PLUS: case OP_MINUS:
1289 case OP_MUL: case OP_DIV: case OP_MOD:
1290 case OP_DOT:
1291 if ($this->t->scanOperand)
1292 break 2;
1293
1294 while ( !empty($operators) &&
1295 $this->opPrecedence[end($operators)->type] >= $this->opPrecedence[$tt]
1296 )
1297 $this->reduce($operators, $operands);
1298
1299 if ($tt == OP_DOT)
1300 {
1301 $this->t->mustMatch(TOKEN_IDENTIFIER);
1302 array_push($operands, new JSNode($this->t, OP_DOT, array_pop($operands), new JSNode($this->t)));
1303 }
1304 else
1305 {
1306 array_push($operators, new JSNode($this->t));
1307 $this->t->scanOperand = true;
1308 }
1309 break;
1310
1311 case KEYWORD_DELETE: case KEYWORD_VOID: case KEYWORD_TYPEOF:
1312 case OP_NOT: case OP_BITWISE_NOT: case OP_UNARY_PLUS: case OP_UNARY_MINUS:
1313 case KEYWORD_NEW:
1314 if (!$this->t->scanOperand)
1315 break 2;
1316
1317 array_push($operators, new JSNode($this->t));
1318 break;
1319
1320 case OP_INCREMENT: case OP_DECREMENT:
1321 if ($this->t->scanOperand)
1322 {
1323 array_push($operators, new JSNode($this->t)); // prefix increment or decrement
1324 }
1325 else
1326 {
1327 // Don't cross a line boundary for postfix {in,de}crement.
1328 $t = $this->t->tokens[($this->t->tokenIndex + $this->t->lookahead - 1) & 3];
1329 if ($t && $t->lineno != $this->t->lineno)
1330 break 2;
1331
1332 if (!empty($operators))
1333 {
1334 // Use >, not >=, so postfix has higher precedence than prefix.
1335 while ($this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt])
1336 $this->reduce($operators, $operands);
1337 }
1338
1339 $n = new JSNode($this->t, $tt, array_pop($operands));
1340 $n->postfix = true;
1341 array_push($operands, $n);
1342 }
1343 break;
1344
1345 case KEYWORD_FUNCTION:
1346 if (!$this->t->scanOperand)
1347 break 2;
1348
1349 array_push($operands, $this->FunctionDefinition($x, false, EXPRESSED_FORM));
1350 $this->t->scanOperand = false;
1351 break;
1352
1353 case KEYWORD_NULL: case KEYWORD_THIS: case KEYWORD_TRUE: case KEYWORD_FALSE:
1354 case TOKEN_IDENTIFIER: case TOKEN_NUMBER: case TOKEN_STRING: case TOKEN_REGEXP:
1355 if (!$this->t->scanOperand)
1356 break 2;
1357
1358 array_push($operands, new JSNode($this->t));
1359 $this->t->scanOperand = false;
1360 break;
1361
1362 case TOKEN_CONDCOMMENT_START:
1363 case TOKEN_CONDCOMMENT_END:
1364 if ($this->t->scanOperand)
1365 array_push($operators, new JSNode($this->t));
1366 else
1367 array_push($operands, new JSNode($this->t));
1368 break;
1369
1370 case OP_LEFT_BRACKET:
1371 if ($this->t->scanOperand)
1372 {
1373 // Array initialiser. Parse using recursive descent, as the
1374 // sub-grammar here is not an operator grammar.
1375 $n = new JSNode($this->t, JS_ARRAY_INIT);
1376 while (($tt = $this->t->peek()) != OP_RIGHT_BRACKET)
1377 {
1378 if ($tt == OP_COMMA)
1379 {
1380 $this->t->get();
1381 $n->addNode(null);
1382 continue;
1383 }
1384
1385 $n->addNode($this->Expression($x, OP_COMMA));
1386 if (!$this->t->match(OP_COMMA))
1387 break;
1388 }
1389
1390 $this->t->mustMatch(OP_RIGHT_BRACKET);
1391 array_push($operands, $n);
1392 $this->t->scanOperand = false;
1393 }
1394 else
1395 {
1396 // Property indexing operator.
1397 array_push($operators, new JSNode($this->t, JS_INDEX));
1398 $this->t->scanOperand = true;
1399 ++$x->bracketLevel;
1400 }
1401 break;
1402
1403 case OP_RIGHT_BRACKET:
1404 if ($this->t->scanOperand || $x->bracketLevel == $bl)
1405 break 2;
1406
1407 while ($this->reduce($operators, $operands)->type != JS_INDEX)
1408 continue;
1409
1410 --$x->bracketLevel;
1411 break;
1412
1413 case OP_LEFT_CURLY:
1414 if (!$this->t->scanOperand)
1415 break 2;
1416
1417 // Object initialiser. As for array initialisers (see above),
1418 // parse using recursive descent.
1419 ++$x->curlyLevel;
1420 $n = new JSNode($this->t, JS_OBJECT_INIT);
1421 while (!$this->t->match(OP_RIGHT_CURLY))
1422 {
1423 do
1424 {
1425 $tt = $this->t->get();
1426 $tv = $this->t->currentToken()->value;
1427 if (($tv == 'get' || $tv == 'set') && $this->t->peek() == TOKEN_IDENTIFIER)
1428 {
1429 if ($x->ecmaStrictMode)
1430 throw $this->t->newSyntaxError('Illegal property accessor');
1431
1432 $n->addNode($this->FunctionDefinition($x, true, EXPRESSED_FORM));
1433 }
1434 else
1435 {
1436 switch ($tt)
1437 {
1438 case TOKEN_IDENTIFIER:
1439 case TOKEN_NUMBER:
1440 case TOKEN_STRING:
1441 $id = new JSNode($this->t);
1442 break;
1443
1444 case OP_RIGHT_CURLY:
1445 if ($x->ecmaStrictMode)
1446 throw $this->t->newSyntaxError('Illegal trailing ,');
1447 break 3;
1448
1449 default:
1450 throw $this->t->newSyntaxError('Invalid property name');
1451 }
1452
1453 $this->t->mustMatch(OP_COLON);
1454 $n->addNode(new JSNode($this->t, JS_PROPERTY_INIT, $id, $this->Expression($x, OP_COMMA)));
1455 }
1456 }
1457 while ($this->t->match(OP_COMMA));
1458
1459 $this->t->mustMatch(OP_RIGHT_CURLY);
1460 break;
1461 }
1462
1463 array_push($operands, $n);
1464 $this->t->scanOperand = false;
1465 --$x->curlyLevel;
1466 break;
1467
1468 case OP_RIGHT_CURLY:
1469 if (!$this->t->scanOperand && $x->curlyLevel != $cl)
1470 throw new Exception('PANIC: right curly botch');
1471 break 2;
1472
1473 case OP_LEFT_PAREN:
1474 if ($this->t->scanOperand)
1475 {
1476 array_push($operators, new JSNode($this->t, JS_GROUP));
1477 }
1478 else
1479 {
1480 while ( !empty($operators) &&
1481 $this->opPrecedence[end($operators)->type] > $this->opPrecedence[KEYWORD_NEW]
1482 )
1483 $this->reduce($operators, $operands);
1484
1485 // Handle () now, to regularize the n-ary case for n > 0.
1486 // We must set scanOperand in case there are arguments and
1487 // the first one is a regexp or unary+/-.
1488 $n = end($operators);
1489 $this->t->scanOperand = true;
1490 if ($this->t->match(OP_RIGHT_PAREN))
1491 {
1492 if ($n && $n->type == KEYWORD_NEW)
1493 {
1494 array_pop($operators);
1495 $n->addNode(array_pop($operands));
1496 }
1497 else
1498 {
1499 $n = new JSNode($this->t, JS_CALL, array_pop($operands), new JSNode($this->t, JS_LIST));
1500 }
1501
1502 array_push($operands, $n);
1503 $this->t->scanOperand = false;
1504 break;
1505 }
1506
1507 if ($n && $n->type == KEYWORD_NEW)
1508 $n->type = JS_NEW_WITH_ARGS;
1509 else
1510 array_push($operators, new JSNode($this->t, JS_CALL));
1511 }
1512
1513 ++$x->parenLevel;
1514 break;
1515
1516 case OP_RIGHT_PAREN:
1517 if ($this->t->scanOperand || $x->parenLevel == $pl)
1518 break 2;
1519
1520 while (($tt = $this->reduce($operators, $operands)->type) != JS_GROUP &&
1521 $tt != JS_CALL && $tt != JS_NEW_WITH_ARGS
1522 )
1523 {
1524 continue;
1525 }
1526
1527 if ($tt != JS_GROUP)
1528 {
1529 $n = end($operands);
1530 if ($n->treeNodes[1]->type != OP_COMMA)
1531 $n->treeNodes[1] = new JSNode($this->t, JS_LIST, $n->treeNodes[1]);
1532 else
1533 $n->treeNodes[1]->type = JS_LIST;
1534 }
1535
1536 --$x->parenLevel;
1537 break;
1538
1539 // Automatic semicolon insertion means we may scan across a newline
1540 // and into the beginning of another statement. If so, break out of
1541 // the while loop and let the t.scanOperand logic handle errors.
1542 default:
1543 break 2;
1544 }
1545 }
1546
1547 if ($x->hookLevel != $hl)
1548 throw $this->t->newSyntaxError('Missing : in conditional expression');
1549
1550 if ($x->parenLevel != $pl)
1551 throw $this->t->newSyntaxError('Missing ) in parenthetical');
1552
1553 if ($x->bracketLevel != $bl)
1554 throw $this->t->newSyntaxError('Missing ] in index expression');
1555
1556 if ($this->t->scanOperand)
1557 throw $this->t->newSyntaxError('Missing operand');
1558
1559 // Resume default mode, scanning for operands, not operators.
1560 $this->t->scanOperand = true;
1561 $this->t->unget();
1562
1563 while (count($operators))
1564 $this->reduce($operators, $operands);
1565
1566 return array_pop($operands);
1567 }
1568
1569 private function ParenExpression($x)
1570 {
1571 $this->t->mustMatch(OP_LEFT_PAREN);
1572 $n = $this->Expression($x);
1573 $this->t->mustMatch(OP_RIGHT_PAREN);
1574
1575 return $n;
1576 }
1577
1578 // Statement stack and nested statement handler.
1579 private function nest($x, $node, $end = false)
1580 {
1581 array_push($x->stmtStack, $node);
1582 $n = $this->statement($x);
1583 array_pop($x->stmtStack);
1584
1585 if ($end)
1586 $this->t->mustMatch($end);
1587
1588 return $n;
1589 }
1590
1591 private function reduce(&$operators, &$operands)
1592 {
1593 $n = array_pop($operators);
1594 $op = $n->type;
1595 $arity = $this->opArity[$op];
1596 $c = count($operands);
1597 if ($arity == -2)
1598 {
1599 // Flatten left-associative trees
1600 if ($c >= 2)
1601 {
1602 $left = $operands[$c - 2];
1603 if ($left->type == $op)
1604 {
1605 $right = array_pop($operands);
1606 $left->addNode($right);
1607 return $left;
1608 }
1609 }
1610 $arity = 2;
1611 }
1612
1613 // Always use push to add operands to n, to update start and end
1614 $a = array_splice($operands, $c - $arity);
1615 for ($i = 0; $i < $arity; $i++)
1616 $n->addNode($a[$i]);
1617
1618 // Include closing bracket or postfix operator in [start,end]
1619 $te = $this->t->currentToken()->end;
1620 if ($n->end < $te)
1621 $n->end = $te;
1622
1623 array_push($operands, $n);
1624
1625 return $n;
1626 }
1627 }
1628
1629 class JSCompilerContext
1630 {
1631 public $inFunction = false;
1632 public $inForLoopInit = false;
1633 public $ecmaStrictMode = false;
1634 public $bracketLevel = 0;
1635 public $curlyLevel = 0;
1636 public $parenLevel = 0;
1637 public $hookLevel = 0;
1638
1639 public $stmtStack = array();
1640 public $funDecls = array();
1641 public $varDecls = array();
1642
1643 public function __construct($inFunction)
1644 {
1645 $this->inFunction = $inFunction;
1646 }
1647 }
1648
1649 class JSNode
1650 {
1651 private $type;
1652 private $value;
1653 private $lineno;
1654 private $start;
1655 private $end;
1656
1657 public $treeNodes = array();
1658 public $funDecls = array();
1659 public $varDecls = array();
1660
1661 public function __construct($t, $type=0)
1662 {
1663 if ($token = $t->currentToken())
1664 {
1665 $this->type = $type ? $type : $token->type;
1666 $this->value = $token->value;
1667 $this->lineno = $token->lineno;
1668 $this->start = $token->start;
1669 $this->end = $token->end;
1670 }
1671 else
1672 {
1673 $this->type = $type;
1674 $this->lineno = $t->lineno;
1675 }
1676
1677 if (($numargs = func_num_args()) > 2)
1678 {
1679 $args = func_get_args();
1680 for ($i = 2; $i < $numargs; $i++)
1681 $this->addNode($args[$i]);
1682 }
1683 }
1684
1685 // we don't want to bloat our object with all kind of specific properties, so we use overloading
1686 public function __set($name, $value)
1687 {
1688 $this->$name = $value;
1689 }
1690
1691 public function __get($name)
1692 {
1693 if (isset($this->$name))
1694 return $this->$name;
1695
1696 return null;
1697 }
1698
1699 public function addNode($node)
1700 {
1701 if ($node !== null)
1702 {
1703 if ($node->start < $this->start)
1704 $this->start = $node->start;
1705 if ($this->end < $node->end)
1706 $this->end = $node->end;
1707 }
1708
1709 $this->treeNodes[] = $node;
1710 }
1711 }
1712
1713 class JSTokenizer
1714 {
1715 private $cursor = 0;
1716 private $source;
1717
1718 public $tokens = array();
1719 public $tokenIndex = 0;
1720 public $lookahead = 0;
1721 public $scanNewlines = false;
1722 public $scanOperand = true;
1723
1724 public $filename;
1725 public $lineno;
1726
1727 private $keywords = array(
1728 'break',
1729 'case', 'catch', 'const', 'continue',
1730 'debugger', 'default', 'delete', 'do',
1731 'else', 'enum',
1732 'false', 'finally', 'for', 'function',
1733 'if', 'in', 'instanceof',
1734 'new', 'null',
1735 'return',
1736 'switch',
1737 'this', 'throw', 'true', 'try', 'typeof',
1738 'var', 'void',
1739 'while', 'with'
1740 );
1741
1742 private $opTypeNames = array(
1743 ';', ',', '?', ':', '||', '&&', '|', '^',
1744 '&', '===', '==', '=', '!==', '!=', '<<', '<=',
1745 '<', '>>>', '>>', '>=', '>', '++', '--', '+',
1746 '-', '*', '/', '%', '!', '~', '.', '[',
1747 ']', '{', '}', '(', ')', '@*/'
1748 );
1749
1750 private $assignOps = array('|', '^', '&', '<<', '>>', '>>>', '+', '-', '*', '/', '%');
1751 private $opRegExp;
1752
1753 public function __construct()
1754 {
1755 $this->opRegExp = '#^(' . implode('|', array_map('preg_quote', $this->opTypeNames)) . ')#';
1756 }
1757
1758 public function init($source, $filename = '', $lineno = 1)
1759 {
1760 $this->source = $source;
1761 $this->filename = $filename ? $filename : '[inline]';
1762 $this->lineno = $lineno;
1763
1764 $this->cursor = 0;
1765 $this->tokens = array();
1766 $this->tokenIndex = 0;
1767 $this->lookahead = 0;
1768 $this->scanNewlines = false;
1769 $this->scanOperand = true;
1770 }
1771
1772 public function getInput($chunksize)
1773 {
1774 if ($chunksize)
1775 return substr($this->source, $this->cursor, $chunksize);
1776
1777 return substr($this->source, $this->cursor);
1778 }
1779
1780 public function isDone()
1781 {
1782 return $this->peek() == TOKEN_END;
1783 }
1784
1785 public function match($tt)
1786 {
1787 return $this->get() == $tt || $this->unget();
1788 }
1789
1790 public function mustMatch($tt)
1791 {
1792 if (!$this->match($tt))
1793 throw $this->newSyntaxError('Unexpected token; token ' . $tt . ' expected');
1794
1795 return $this->currentToken();
1796 }
1797
1798 public function peek()
1799 {
1800 if ($this->lookahead)
1801 {
1802 $next = $this->tokens[($this->tokenIndex + $this->lookahead) & 3];
1803 if ($this->scanNewlines && $next->lineno != $this->lineno)
1804 $tt = TOKEN_NEWLINE;
1805 else
1806 $tt = $next->type;
1807 }
1808 else
1809 {
1810 $tt = $this->get();
1811 $this->unget();
1812 }
1813
1814 return $tt;
1815 }
1816
1817 public function peekOnSameLine()
1818 {
1819 $this->scanNewlines = true;
1820 $tt = $this->peek();
1821 $this->scanNewlines = false;
1822
1823 return $tt;
1824 }
1825
1826 public function currentToken()
1827 {
1828 if (!empty($this->tokens))
1829 return $this->tokens[$this->tokenIndex];
1830 }
1831
1832 public function get($chunksize = 1000)
1833 {
1834 while($this->lookahead)
1835 {
1836 $this->lookahead--;
1837 $this->tokenIndex = ($this->tokenIndex + 1) & 3;
1838 $token = $this->tokens[$this->tokenIndex];
1839 if ($token->type != TOKEN_NEWLINE || $this->scanNewlines)
1840 return $token->type;
1841 }
1842
1843 $conditional_comment = false;
1844
1845 // strip whitespace and comments
1846 while(true)
1847 {
1848 $input = $this->getInput($chunksize);
1849
1850 // whitespace handling; gobble up \r as well (effectively we don't have support for MAC newlines!)
1851 $re = $this->scanNewlines ? '/^[ \r\t]+/' : '/^\s+/';
1852 if (preg_match($re, $input, $match))
1853 {
1854 $spaces = $match[0];
1855 $spacelen = strlen($spaces);
1856 $this->cursor += $spacelen;
1857 if (!$this->scanNewlines)
1858 $this->lineno += substr_count($spaces, "\n");
1859
1860 if ($spacelen == $chunksize)
1861 continue; // complete chunk contained whitespace
1862
1863 $input = $this->getInput($chunksize);
1864 if ($input == '' || $input[0] != '/')
1865 break;
1866 }
1867
1868 // Comments
1869 if (!preg_match('/^\/(?:\*(@(?:cc_on|if|elif|else|end))?.*?\*\/|\/[^\n]*)/s', $input, $match))
1870 {
1871 if (!$chunksize)
1872 break;
1873
1874 // retry with a full chunk fetch; this also prevents breakage of long regular expressions (which will never match a comment)
1875 $chunksize = null;
1876 continue;
1877 }
1878
1879 // check if this is a conditional (JScript) comment
1880 if (!empty($match[1]))
1881 {
1882 $match[0] = '/*' . $match[1];
1883 $conditional_comment = true;
1884 break;
1885 }
1886 else
1887 {
1888 $this->cursor += strlen($match[0]);
1889 $this->lineno += substr_count($match[0], "\n");
1890 }
1891 }
1892
1893 if ($input == '')
1894 {
1895 $tt = TOKEN_END;
1896 $match = array('');
1897 }
1898 elseif ($conditional_comment)
1899 {
1900 $tt = TOKEN_CONDCOMMENT_START;
1901 }
1902 else
1903 {
1904 switch ($input[0])
1905 {
1906 case '0':
1907 // hexadecimal
1908 if (($input[1] == 'x' || $input[1] == 'X') && preg_match('/^0x[0-9a-f]+/i', $input, $match))
1909 {
1910 $tt = TOKEN_NUMBER;
1911 break;
1912 }
1913 // FALL THROUGH
1914
1915 case '1': case '2': case '3': case '4': case '5':
1916 case '6': case '7': case '8': case '9':
1917 // should always match
1918 preg_match('/^\d+(?:\.\d*)?(?:[eE][-+]?\d+)?/', $input, $match);
1919 $tt = TOKEN_NUMBER;
1920 break;
1921
1922 case "'":
1923 if (preg_match('/^\'(?:[^\\\\\'\r\n]++|\\\\(?:.|\r?\n))*\'/', $input, $match))
1924 {
1925 $tt = TOKEN_STRING;
1926 }
1927 else
1928 {
1929 if ($chunksize)
1930 return $this->get(null); // retry with a full chunk fetch
1931
1932 throw $this->newSyntaxError('Unterminated string literal');
1933 }
1934 break;
1935
1936 case '"':
1937 if (preg_match('/^"(?:[^\\\\"\r\n]++|\\\\(?:.|\r?\n))*"/', $input, $match))
1938 {
1939 $tt = TOKEN_STRING;
1940 }
1941 else
1942 {
1943 if ($chunksize)
1944 return $this->get(null); // retry with a full chunk fetch
1945
1946 throw $this->newSyntaxError('Unterminated string literal');
1947 }
1948 break;
1949
1950 case '/':
1951 if ($this->scanOperand && preg_match('/^\/((?:\\\\.|\[(?:\\\\.|[^\]])*\]|[^\/])+)\/([gimy]*)/', $input, $match))
1952 {
1953 $tt = TOKEN_REGEXP;
1954 break;
1955 }
1956 // FALL THROUGH
1957
1958 case '|':
1959 case '^':
1960 case '&':
1961 case '<':
1962 case '>':
1963 case '+':
1964 case '-':
1965 case '*':
1966 case '%':
1967 case '=':
1968 case '!':
1969 // should always match
1970 preg_match($this->opRegExp, $input, $match);
1971 $op = $match[0];
1972 if (in_array($op, $this->assignOps) && $input[strlen($op)] == '=')
1973 {
1974 $tt = OP_ASSIGN;
1975 $match[0] .= '=';
1976 }
1977 else
1978 {
1979 $tt = $op;
1980 if ($this->scanOperand)
1981 {
1982 if ($op == OP_PLUS)
1983 $tt = OP_UNARY_PLUS;
1984 elseif ($op == OP_MINUS)
1985 $tt = OP_UNARY_MINUS;
1986 }
1987 $op = null;
1988 }
1989 break;
1990
1991 case '.':
1992 if (preg_match('/^\.\d+(?:[eE][-+]?\d+)?/', $input, $match))
1993 {
1994 $tt = TOKEN_NUMBER;
1995 break;
1996 }
1997 // FALL THROUGH
1998
1999 case ';':
2000 case ',':
2001 case '?':
2002 case ':':
2003 case '~':
2004 case '[':
2005 case ']':
2006 case '{':
2007 case '}':
2008 case '(':
2009 case ')':
2010 // these are all single
2011 $match = array($input[0]);
2012 $tt = $input[0];
2013 break;
2014
2015 case '@':
2016 // check end of conditional comment
2017 if (substr($input, 0, 3) == '@*/')
2018 {
2019 $match = array('@*/');
2020 $tt = TOKEN_CONDCOMMENT_END;
2021 }
2022 else
2023 throw $this->newSyntaxError('Illegal token');
2024 break;
2025
2026 case "\n":
2027 if ($this->scanNewlines)
2028 {
2029 $match = array("\n");
2030 $tt = TOKEN_NEWLINE;
2031 }
2032 else
2033 throw $this->newSyntaxError('Illegal token');
2034 break;
2035
2036 default:
2037 // FIXME: add support for unicode and unicode escape sequence \uHHHH
2038 if (preg_match('/^[$\w]+/', $input, $match))
2039 {
2040 $tt = in_array($match[0], $this->keywords) ? $match[0] : TOKEN_IDENTIFIER;
2041 }
2042 else
2043 throw $this->newSyntaxError('Illegal token');
2044 }
2045 }
2046
2047 $this->tokenIndex = ($this->tokenIndex + 1) & 3;
2048
2049 if (!isset($this->tokens[$this->tokenIndex]))
2050 $this->tokens[$this->tokenIndex] = new JSToken();
2051
2052 $token = $this->tokens[$this->tokenIndex];
2053 $token->type = $tt;
2054
2055 if ($tt == OP_ASSIGN)
2056 $token->assignOp = $op;
2057
2058 $token->start = $this->cursor;
2059
2060 $token->value = $match[0];
2061 $this->cursor += strlen($match[0]);
2062
2063 $token->end = $this->cursor;
2064 $token->lineno = $this->lineno;
2065
2066 return $tt;
2067 }
2068
2069 public function unget()
2070 {
2071 if (++$this->lookahead == 4)
2072 throw $this->newSyntaxError('PANIC: too much lookahead!');
2073
2074 $this->tokenIndex = ($this->tokenIndex - 1) & 3;
2075 }
2076
2077 public function newSyntaxError($m)
2078 {
2079 return new Exception('Parse error: ' . $m . ' in file \'' . $this->filename . '\' on line ' . $this->lineno);
2080 }
2081 }
2082
2083 class JSToken
2084 {
2085 public $type;
2086 public $value;
2087 public $start;
2088 public $end;
2089 public $lineno;
2090 public $assignOp;
2091 }
2092