Skip to content
This repository
Browse code

Extract regular expressions, lexer and some state variables.

This is my first attempt on making JSHint more refactorable. I started
with removing the giant closure (browserify will add something like
that anyway) and moving some parts into separate modules:

 1. Lexer (lex) is now in lex.js
 2. Long regular expressions (tx, ax, etc.) are now in reg.js with
    better names.
 3. Lexer shares some state variables with other globals. I moved
    those into a state.js file.

The code is somewhat hacky right now but it opens a door towards easier
refactoring for me.

Surprised I didn't get grey hair while debugging all corner cases.
  • Loading branch information...
commit 684b3794796d004b86cb5c0df85324158cd3a049 1 parent b06a3cf
Anton Kovalyov authored November 03, 2012
1,960  src/stable/jshint.js
@@ -32,6 +32,9 @@
32 32
 
33 33
 var vars = require("../shared/vars.js");
34 34
 var messages = require("../shared/messages.js");
  35
+var lex = require("./lex.js").lex;
  36
+var reg = require("./reg.js");
  37
+var state = require("./state.js").state;
35 38
 
36 39
 // We build the application inside a function so that we produce only a single
37 40
 // global variable. That function will be invoked immediately, and its return
@@ -193,60 +196,19 @@ var JSHINT = (function () {
193 196
 		implied, // Implied globals
194 197
 		inblock,
195 198
 		indent,
196  
-		jsonmode,
197  
-		lines,
198 199
 		lookahead,
199 200
 		member,
200 201
 		membersOnly,
201  
-		nexttoken,
202 202
 		noreach,
203  
-		option,
204 203
 		predefined,		// Global variables defined by option
205  
-		prereg,
206  
-		prevtoken,
207  
-		quotmark,
  204
+
208 205
 		scope,  // The current scope
209 206
 		stack,
210  
-		directive,
211  
-		syntax = {},
212  
-		tab,
213  
-		token,
214 207
 		unuseds,
215 208
 		urls,
216 209
 		useESNextSyntax,
217 210
 		warnings;
218 211
 
219  
-	// Regular expressions. Some of these are stupidly long.
220  
-	var ax, cx, tx, nx, nxg, lx, ix, jx, ft;
221  
-	(function () {
222  
-		/*jshint maxlen:300 */
223  
-
224  
-		// unsafe comment or string
225  
-		ax = /@cc|<\/?|script|\]\s*\]|<\s*!|&lt/i;
226  
-
227  
-		// unsafe characters that are silently deleted by one or more browsers
228  
-		cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/;
229  
-
230  
-		// token
231  
-		tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/=(?!(\S*\/[gim]?))|\/(\*(jshint|jslint|members?|global)?|\/)?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/;
232  
-
233  
-		// characters in strings that need escapement
234  
-		nx = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/;
235  
-		nxg = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
236  
-
237  
-		// star slash
238  
-		lx = /\*\//;
239  
-
240  
-		// identifier
241  
-		ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/;
242  
-
243  
-		// javascript url
244  
-		jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i;
245  
-
246  
-		// catches /* falls through */ comments
247  
-		ft = /^\s*\/\*\s*falls\sthrough\s*\*\/\s*$/;
248  
-	}());
249  
-
250 212
 	function F() {}		// Used by Object.create
251 213
 
252 214
 	function is_own(object, name) {
@@ -342,20 +304,11 @@ var JSHINT = (function () {
342 304
 
343 305
 	// Non standard methods
344 306
 
345  
-	function isAlpha(str) {
346  
-		return (str >= "a" && str <= "z\uffff") ||
347  
-			(str >= "A" && str <= "Z\uffff");
348  
-	}
349  
-
350  
-	function isDigit(str) {
351  
-		return (str >= "0" && str <= "9");
352  
-	}
353  
-
354  
-	function isIdentifier(token, value) {
355  
-		if (!token)
  307
+	function isIdentifier(tkn, value) {
  308
+		if (!tkn)
356 309
 			return false;
357 310
 
358  
-		if (!token.identifier || token.value !== value)
  311
+		if (!tkn.identifier || tkn.value !== value)
359 312
 			return false;
360 313
 
361 314
 		return true;
@@ -384,63 +337,63 @@ var JSHINT = (function () {
384 337
 	}
385 338
 
386 339
 	function assume() {
387  
-		if (option.couch) {
  340
+		if (state.option.couch) {
388 341
 			combine(predefined, vars.couch);
389 342
 		}
390 343
 
391  
-		if (option.rhino) {
  344
+		if (state.option.rhino) {
392 345
 			combine(predefined, vars.rhino);
393 346
 		}
394 347
 
395  
-		if (option.prototypejs) {
  348
+		if (state.option.prototypejs) {
396 349
 			combine(predefined, vars.prototypejs);
397 350
 		}
398 351
 
399  
-		if (option.node) {
  352
+		if (state.option.node) {
400 353
 			combine(predefined, vars.node);
401 354
 		}
402 355
 
403  
-		if (option.devel) {
  356
+		if (state.option.devel) {
404 357
 			combine(predefined, vars.devel);
405 358
 		}
406 359
 
407  
-		if (option.dojo) {
  360
+		if (state.option.dojo) {
408 361
 			combine(predefined, vars.dojo);
409 362
 		}
410 363
 
411  
-		if (option.browser) {
  364
+		if (state.option.browser) {
412 365
 			combine(predefined, vars.browser);
413 366
 		}
414 367
 
415  
-		if (option.nonstandard) {
  368
+		if (state.option.nonstandard) {
416 369
 			combine(predefined, vars.nonstandard);
417 370
 		}
418 371
 
419  
-		if (option.jquery) {
  372
+		if (state.option.jquery) {
420 373
 			combine(predefined, vars.jquery);
421 374
 		}
422 375
 
423  
-		if (option.mootools) {
  376
+		if (state.option.mootools) {
424 377
 			combine(predefined, vars.mootools);
425 378
 		}
426 379
 
427  
-		if (option.worker) {
  380
+		if (state.option.worker) {
428 381
 			combine(predefined, vars.worker);
429 382
 		}
430 383
 
431  
-		if (option.wsh) {
  384
+		if (state.option.wsh) {
432 385
 			combine(predefined, vars.wsh);
433 386
 		}
434 387
 
435  
-		if (option.esnext) {
  388
+		if (state.option.esnext) {
436 389
 			useESNextSyntax();
437 390
 		}
438 391
 
439  
-		if (option.globalstrict && option.strict !== false) {
440  
-			option.strict = true;
  392
+		if (state.option.globalstrict && state.option.strict !== false) {
  393
+			state.option.strict = true;
441 394
 		}
442 395
 
443  
-		if (option.yui) {
  396
+		if (state.option.yui) {
444 397
 			combine(predefined, vars.yui);
445 398
 		}
446 399
 	}
@@ -448,7 +401,7 @@ var JSHINT = (function () {
448 401
 
449 402
 	// Produce an error warning.
450 403
 	function quit(message, line, chr) {
451  
-		var percentage = Math.floor((line / lines.length) * 100);
  404
+		var percentage = Math.floor((line / state.lines.length) * 100);
452 405
 
453 406
 		throw {
454 407
 			name: "JSHintError",
@@ -476,9 +429,9 @@ var JSHINT = (function () {
476 429
 			msg = { code: "W000", desc: code };
477 430
 		}
478 431
 
479  
-		t = t || nexttoken;
  432
+		t = t || state.tokens.next;
480 433
 		if (t.id === "(end)") {  // `~
481  
-			t = token;
  434
+			t = state.tokens.curr;
482 435
 		}
483 436
 
484 437
 		l = t.line || 0;
@@ -488,7 +441,7 @@ var JSHINT = (function () {
488 441
 			id: "(error)",
489 442
 			raw: msg.desc,
490 443
 			code: msg.code,
491  
-			evidence: lines[l - 1] || "",
  444
+			evidence: state.lines[l - 1] || "",
492 445
 			line: l,
493 446
 			character: ch,
494 447
 			scope: JSHINT.scope,
@@ -501,12 +454,12 @@ var JSHINT = (function () {
501 454
 		w.reason = supplant(msg.desc, w);
502 455
 		JSHINT.errors.push(w);
503 456
 
504  
-		if (option.passfail) {
  457
+		if (state.option.passfail) {
505 458
 			quit("Stopping. ", l, ch);
506 459
 		}
507 460
 
508 461
 		warnings += 1;
509  
-		if (warnings >= option.maxerr) {
  462
+		if (warnings >= state.option.maxerr) {
510 463
 			quit("Too many errors.", l, ch);
511 464
 		}
512 465
 
@@ -543,813 +496,7 @@ var JSHINT = (function () {
543 496
 		return i;
544 497
 	}
545 498
 
546  
-
547  
-	/*
548  
-	 * Lexical analysis and token construction.
549  
-	 */
550  
-	var lex = (function lex() {
551  
-		var character, from, line, input;
552  
-
553  
-		// Private lex methods
554  
-
555  
-		function nextLine() {
556  
-			var at, match;
557  
-
558  
-			if (line >= lines.length) {
559  
-				return false;
560  
-			}
561  
-
562  
-			character = 1;
563  
-			input = lines[line];
564  
-			line += 1;
565  
-
566  
-			// If smarttabs option is used check for spaces followed by tabs only.
567  
-			// Otherwise check for any occurence of mixed tabs and spaces.
568  
-			// Tabs and one space followed by block comment is allowed.
569  
-
570  
-			if (option.smarttabs) {
571  
-				// negative look-behind for "//"
572  
-				match = input.match(/(\/\/)? \t/);
573  
-				at = match && !match[1] ? 0 : -1;
574  
-			} else {
575  
-				at = input.search(/ \t|\t [^\*]/);
576  
-			}
577  
-
578  
-			// Warn about mixed spaces and tabs.
579  
-
580  
-			if (at >= 0) {
581  
-				warningAt("W099", line, at + 1);
582  
-			}
583  
-
584  
-			input = input.replace(/\t/g, tab);
585  
-
586  
-			// Warn about unsafe characters that get silently deleted by one
587  
-			// or more browsers.
588  
-
589  
-			at = input.search(cx);
590  
-			if (at >= 0) {
591  
-				warningAt("W100", line, at);
592  
-			}
593  
-
594  
-			// If there is a limit on line length, warn when lines get too
595  
-			// long.
596  
-
597  
-			if (option.maxlen && option.maxlen < input.length) {
598  
-				warningAt("W101", line, input.length);
599  
-			}
600  
-
601  
-			// Check for trailing whitespaces
602  
-
603  
-			var tw = option.trailing && input.match(/^(.*?)\s+$/);
604  
-			if (tw && !/^\s+$/.test(input)) {
605  
-				warningAt("W102", line, tw[1].length + 1);
606  
-			}
607  
-
608  
-			return true;
609  
-		}
610  
-
611  
-		/*
612  
-		 * Produce a token object. The token inherits from a syntax symbol.
613  
-		 */
614  
-		function it(type, value) {
615  
-			var i, t;
616  
-
617  
-			function checkName(name) {
618  
-				if (!option.proto && name === "__proto__") {
619  
-					warningAt("W103", line, from, name);
620  
-					return;
621  
-				}
622  
-
623  
-				if (!option.iterator && name === "__iterator__") {
624  
-					warningAt("W104", line, from, name);
625  
-					return;
626  
-				}
627  
-
628  
-				// Check for dangling underscores unless we're in Node
629  
-				// environment and this identifier represents built-in
630  
-				// Node globals with underscores.
631  
-
632  
-				var hasDangling = /^(_+.*|.*_+)$/.test(name);
633  
-
634  
-				if (option.nomen && hasDangling && name !== "_") {
635  
-					if (option.node && token.id !== "." && /^(__dirname|__filename)$/.test(name))
636  
-						return;
637  
-
638  
-					warningAt("W105", line, from, "dangling '_'", name);
639  
-					return;
640  
-				}
641  
-
642  
-				// Check for non-camelcase names. Names like MY_VAR and
643  
-				// _myVar are okay though.
644  
-
645  
-				if (option.camelcase) {
646  
-					if (name.replace(/^_+/, "").indexOf("_") > -1 && !name.match(/^[A-Z0-9_]*$/)) {
647  
-						warningAt("W106", line, from, value);
648  
-					}
649  
-				}
650  
-			}
651  
-
652  
-			if (type === "(range)") {
653  
-				t = {type: type};
654  
-			} else if (type === "(punctuator)" ||
655  
-					(type === "(identifier)" && is_own(syntax, value))) {
656  
-				t = syntax[value] || syntax["(error)"];
657  
-			} else {
658  
-				t = syntax[type];
659  
-			}
660  
-
661  
-			t = Object.create(t);
662  
-
663  
-			if (type === "(string)" || type === "(range)") {
664  
-				if (!option.scripturl && jx.test(value)) {
665  
-					warningAt("W107", line, from);
666  
-				}
667  
-			}
668  
-
669  
-			if (type === "(identifier)") {
670  
-				t.identifier = true;
671  
-				checkName(value);
672  
-			}
673  
-
674  
-			t.value = value;
675  
-			t.line = line;
676  
-			t.character = character;
677  
-			t.from = from;
678  
-			i = t.id;
679  
-			if (i !== "(endline)") {
680  
-				prereg = i &&
681  
-					(("(,=:[!&|?{};".indexOf(i.charAt(i.length - 1)) >= 0) ||
682  
-					i === "return" ||
683  
-					i === "case");
684  
-			}
685  
-			return t;
686  
-		}
687  
-
688  
-		// Public lex methods
689  
-
690  
-		return {
691  
-			init: function (source) {
692  
-				if (typeof source === "string") {
693  
-					lines = source
694  
-						.replace(/\r\n/g, "\n")
695  
-						.replace(/\r/g, "\n")
696  
-						.split("\n");
697  
-				} else {
698  
-					lines = source;
699  
-				}
700  
-
701  
-				// If the first line is a shebang (#!), make it a blank and move on.
702  
-				// Shebangs are used by Node scripts.
703  
-				if (lines[0] && lines[0].substr(0, 2) === "#!")
704  
-					lines[0] = "";
705  
-
706  
-				line = 0;
707  
-				nextLine();
708  
-				from = 1;
709  
-			},
710  
-
711  
-			range: function (begin, end) {
712  
-				var c, value = "";
713  
-				from = character;
714  
-
715  
-				if (input.charAt(0) !== begin) {
716  
-					errorAt("E004", line, character, begin, input.charAt(0));
717  
-				}
718  
-
719  
-				for (;;) {
720  
-					input = input.slice(1);
721  
-					character += 1;
722  
-					c = input.charAt(0);
723  
-
724  
-					switch (c) {
725  
-					case "":
726  
-						errorAt("E013", line, character, c);
727  
-						break;
728  
-					case end:
729  
-						input = input.slice(1);
730  
-						character += 1;
731  
-						return it("(range)", value);
732  
-					case "\\":
733  
-						warningAt("W052", line, character, c);
734  
-					}
735  
-
736  
-					value += c;
737  
-				}
738  
-
739  
-			},
740  
-
741  
-			/*
742  
-			 * Produce the next token.
743  
-			 *
744  
-			 * This function is called by advance() to get the next token.
745  
-			 */
746  
-			token: function () {
747  
-				var b, c, captures, d, depth, high, i, l, low, q, t, isLiteral, isInRange, n;
748  
-
749  
-				function match(x) {
750  
-					var r = x.exec(input), r1;
751  
-
752  
-					if (r) {
753  
-						l = r[0].length;
754  
-						r1 = r[1];
755  
-						c = r1.charAt(0);
756  
-						input = input.substr(l);
757  
-						from = character + l - r1.length;
758  
-						character += l;
759  
-						return r1;
760  
-					}
761  
-				}
762  
-
763  
-				function string(x) {
764  
-					var c, j, r = "", allowNewLine = false;
765  
-
766  
-					// In JSON mode all strings must use double-quote.
767  
-
768  
-					if (jsonmode && x !== "\"") {
769  
-						warningAt("W108", line, character);
770  
-					}
771  
-
772  
-					// Option 'quotmark' helps you to enforce one particular
773  
-					// style of quoting.
774  
-
775  
-					var code;
776  
-					if (option.quotmark) {
777  
-						switch (true) {
778  
-						case option.quotmark === "single" && x !== "'":
779  
-							code = "W109";
780  
-							break;
781  
-						case option.quotmark === "double" && x !== "\"":
782  
-							code = "W108";
783  
-							break;
784  
-						case option.quotmark === true:
785  
-							// If quotmark is set to true, we remember the very first
786  
-							// quotation style and then use it as a reference.
787  
-							quotmark = quotmark || x;
788  
-
789  
-							// Warn about mixed double and single quotes.
790  
-							if (quotmark !== x) {
791  
-								code = "W110";
792  
-							}
793  
-						}
794  
-
795  
-						if (code) {
796  
-							warningAt(code, line, character);
797  
-						}
798  
-					}
799  
-
800  
-					function esc(n) {
801  
-						var i = parseInt(input.substr(j + 1, n), 16);
802  
-						j += n;
803  
-
804  
-						// Warn about unnecessary escapements.
805  
-						if (i >= 32 && i <= 126 && i !== 34 && i !== 92 && i !== 39) {
806  
-							warningAt("W111", line, character);
807  
-						}
808  
-
809  
-						character += n;
810  
-						c = String.fromCharCode(i);
811  
-					}
812  
-
813  
-					j = 0;
814  
-
815  
-unclosedString:
816  
-					for (;;) {
817  
-						while (j >= input.length) {
818  
-							j = 0;
819  
-
820  
-							var cl = line;
821  
-							var cf = from;
822  
-
823  
-							if (!nextLine()) {
824  
-								// Display an error about an unclosed string.
825  
-								errorAt("E044", cl, cf);
826  
-								break unclosedString;
827  
-							}
828  
-
829  
-							if (allowNewLine) {
830  
-								allowNewLine = false;
831  
-							} else {
832  
-								warningAt("W112", cl, cf); // Warn about an unclosed string.
833  
-							}
834  
-						}
835  
-
836  
-						c = input.charAt(j);
837  
-						if (c === x) {
838  
-							character += 1;
839  
-							input = input.substr(j + 1);
840  
-							return it("(string)", r, x);
841  
-						}
842  
-
843  
-						if (c < " ") {
844  
-							if (c === "\n" || c === "\r") {
845  
-								break;
846  
-							}
847  
-
848  
-							// Warn about a control character in a string.
849  
-							warningAt("W113", line, character + j, input.slice(0, j));
850  
-						} else if (c === "\\") {
851  
-							j += 1;
852  
-							character += 1;
853  
-							c = input.charAt(j);
854  
-							n = input.charAt(j + 1);
855  
-							switch (c) {
856  
-							case "\\":
857  
-							case "\"":
858  
-							case "/":
859  
-								break;
860  
-							case "\'":
861  
-								if (jsonmode) {
862  
-									warningAt("W114", line, character, "\\'");
863  
-								}
864  
-								break;
865  
-							case "b":
866  
-								c = "\b";
867  
-								break;
868  
-							case "f":
869  
-								c = "\f";
870  
-								break;
871  
-							case "n":
872  
-								c = "\n";
873  
-								break;
874  
-							case "r":
875  
-								c = "\r";
876  
-								break;
877  
-							case "t":
878  
-								c = "\t";
879  
-								break;
880  
-							case "0":
881  
-								c = "\0";
882  
-
883  
-								// Octal literals fail in strict mode
884  
-								// check if the number is between 00 and 07
885  
-								// where 'n' is the token next to 'c'
886  
-
887  
-								if (n >= 0 && n <= 7 && directive["use strict"]) {
888  
-									warningAt("W115", line, character);
889  
-								}
890  
-
891  
-								break;
892  
-							case "u":
893  
-								esc(4);
894  
-								break;
895  
-							case "v":
896  
-								if (jsonmode) {
897  
-									warningAt("W114", line, character, "\\v");
898  
-								}
899  
-
900  
-								c = "\v";
901  
-								break;
902  
-							case "x":
903  
-								if (jsonmode) {
904  
-									warningAt("W114", line, character, "\\x-");
905  
-								}
906  
-
907  
-								esc(2);
908  
-								break;
909  
-							case "":
910  
-								// last character is escape character
911  
-								// always allow new line if escaped, but show
912  
-								// warning if option is not set
913  
-								allowNewLine = true;
914  
-								if (option.multistr) {
915  
-									if (jsonmode) {
916  
-										warningAt("W116", line, character);
917  
-									}
918  
-
919  
-									c = "";
920  
-									character -= 1;
921  
-									break;
922  
-								}
923  
-
924  
-								warningAt("W117", line, character);
925  
-								break;
926  
-							case "!":
927  
-								if (input.charAt(j - 2) === "<")
928  
-									break;
929  
-
930  
-								/*falls through*/
931  
-							default:
932  
-								// Weird escapement, warn about that.
933  
-								warningAt("W118", line, character);
934  
-							}
935  
-						}
936  
-
937  
-						r += c;
938  
-						character += 1;
939  
-						j += 1;
940  
-					}
941  
-				}
942  
-
943  
-				for (;;) {
944  
-					if (!input) {
945  
-						return it(nextLine() ? "(endline)" : "(end)", "");
946  
-					}
947  
-
948  
-					t = match(tx);
949  
-
950  
-					if (!t) {
951  
-						t = "";
952  
-						c = "";
953  
-
954  
-						while (input && input < "!") {
955  
-							input = input.substr(1);
956  
-						}
957  
-
958  
-						if (input) {
959  
-							errorAt("E014", line, character, input.substr(0, 1));
960  
-							input = "";
961  
-						}
962  
-					} else {
963  
-
964  
-						// Identifier
965  
-
966  
-						if (isAlpha(c) || c === "_" || c === "$") {
967  
-							return it("(identifier)", t);
968  
-						}
969  
-
970  
-						// Number
971  
-
972  
-						if (isDigit(c)) {
973  
-
974  
-							// Check if this number is invalid.
975  
-
976  
-							if (!isFinite(Number(t))) {
977  
-								warningAt("W119", line, character, t);
978  
-							}
979  
-
980  
-							if (isAlpha(input.substr(0, 1))) {
981  
-								warningAt("W013", line, character, t);
982  
-							}
983  
-
984  
-							if (c === "0") {
985  
-								d = t.substr(1, 1);
986  
-								if (isDigit(d)) {
987  
-									// Check for leading zeroes.
988  
-									if (token.id !== ".") {
989  
-										warningAt("W120", line, character, t);
990  
-									}
991  
-								} else if (jsonmode && (d === "x" || d === "X")) {
992  
-									warningAt("W114", line, character, "0x-");
993  
-								}
994  
-							}
995  
-
996  
-							if (t.substr(t.length - 1) === ".") {
997  
-								// Warn about a trailing decimal point.
998  
-								warningAt("W121", line, character, t);
999  
-							}
1000  
-
1001  
-							return it("(number)", t);
1002  
-						}
1003  
-
1004  
-						switch (t) {
1005  
-
1006  
-						// String
1007  
-
1008  
-						case "\"":
1009  
-						case "'":
1010  
-							return string(t);
1011  
-
1012  
-						// Single line comment
1013  
-
1014  
-						case "//":
1015  
-							input = "";
1016  
-							token.comment = true;
1017  
-							break;
1018  
-
1019  
-						// Block comment
1020  
-
1021  
-						case "/*":
1022  
-							for (;;) {
1023  
-								i = input.search(lx);
1024  
-								if (i >= 0) {
1025  
-									break;
1026  
-								}
1027  
-
1028  
-								// Is this comment unclosed?
1029  
-								if (!nextLine()) {
1030  
-									errorAt("E015", line, character);
1031  
-								}
1032  
-							}
1033  
-
1034  
-							input = input.substr(i + 2);
1035  
-							token.comment = true;
1036  
-							break;
1037  
-
1038  
-						//		/*members /*jshint /*global
1039  
-
1040  
-						case "/*members":
1041  
-						case "/*member":
1042  
-						case "/*jshint":
1043  
-						case "/*jslint":
1044  
-						case "/*global":
1045  
-						case "*/":
1046  
-							return {
1047  
-								value: t,
1048  
-								type: "special",
1049  
-								line: line,
1050  
-								character: character,
1051  
-								from: from
1052  
-							};
1053  
-
1054  
-						case "":
1055  
-							break;
1056  
-
1057  
-						//		/
1058  
-
1059  
-						case "/":
1060  
-							// Warn about '/=' (it can be confused with /= operator.
1061  
-							if (input.charAt(0) === "=") {
1062  
-								errorAt("E016", line, from);
1063  
-							}
1064  
-
1065  
-							if (prereg) {
1066  
-								depth = 0;
1067  
-								captures = 0;
1068  
-								l = 0;
1069  
-								for (;;) {
1070  
-									b = true;
1071  
-									c = input.charAt(l);
1072  
-									l += 1;
1073  
-									switch (c) {
1074  
-									case "":
1075  
-										// Fatal: unclosed regular expression.
1076  
-										errorAt("E017", line, from);
1077  
-										return quit("Stopping.", line, from);
1078  
-									case "/":
1079  
-										// Check that all regexp groups were terminated.
1080  
-										if (depth > 0) {
1081  
-											warningAt("W122", line, from + l, depth);
1082  
-										}
1083  
-
1084  
-										c = input.substr(0, l - 1);
1085  
-
1086  
-										q = {
1087  
-											g: true,
1088  
-											i: true,
1089  
-											m: true
1090  
-										};
1091  
-
1092  
-										while (q[input.charAt(l)] === true) {
1093  
-											q[input.charAt(l)] = false;
1094  
-											l += 1;
1095  
-										}
1096  
-
1097  
-										character += l;
1098  
-										input = input.substr(l);
1099  
-										q = input.charAt(0);
1100  
-
1101  
-										if (q === "/" || q === "*") {
1102  
-											errorAt("E018", line, from);
1103  
-										}
1104  
-
1105  
-										return it("(regexp)", c);
1106  
-									case "\\":
1107  
-										c = input.charAt(l);
1108  
-
1109  
-										if (c < " ") {
1110  
-											// Unexpected control character.
1111  
-											warningAt("W123", line, from + l);
1112  
-										} else if (c === "<") {
1113  
-											// Unexpected escaped character.
1114  
-											warningAt("W124", line, from + l, c);
1115  
-										}
1116  
-
1117  
-										l += 1;
1118  
-										break;
1119  
-									case "(":
1120  
-										depth += 1;
1121  
-										b = false;
1122  
-										if (input.charAt(l) === "?") {
1123  
-											l += 1;
1124  
-											switch (input.charAt(l)) {
1125  
-											case ":":
1126  
-											case "=":
1127  
-											case "!":
1128  
-												l += 1;
1129  
-												break;
1130  
-											default:
1131  
-												warningAt("W132", line, from + l, ":", input.charAt(l));
1132  
-											}
1133  
-										} else {
1134  
-											captures += 1;
1135  
-										}
1136  
-										break;
1137  
-									case "|":
1138  
-										b = false;
1139  
-										break;
1140  
-									case ")":
1141  
-										if (depth === 0) {
1142  
-											// Warn about unexpected paren.
1143  
-											warningAt("W125", line, from + l, ")");
1144  
-										} else {
1145  
-											depth -= 1;
1146  
-										}
1147  
-										break;
1148  
-									case " ":
1149  
-										q = 1;
1150  
-										while (input.charAt(l) === " ") {
1151  
-											l += 1;
1152  
-											q += 1;
1153  
-										}
1154  
-										if (q > 1) {
1155  
-											warningAt("W126", line, from + l, q);
1156  
-										}
1157  
-										break;
1158  
-									case "[":
1159  
-										c = input.charAt(l);
1160  
-										if (c === "^") {
1161  
-											l += 1;
1162  
-											if (input.charAt(l) === "]") {
1163  
-												errorAt("E019", line, from + l, "^");
1164  
-											}
1165  
-										}
1166  
-										if (c === "]") {
1167  
-											warningAt("W127", line, from + l - 1);
1168  
-										}
1169  
-										isLiteral = false;
1170  
-										isInRange = false;
1171  
-klass:
1172  
-										do {
1173  
-											c = input.charAt(l);
1174  
-											l += 1;
1175  
-											switch (c) {
1176  
-											case "[":
1177  
-											case "^":
1178  
-												warningAt("W125", line, from + l, c);
1179  
-
1180  
-												if (isInRange) {
1181  
-													isInRange = false;
1182  
-												} else {
1183  
-													isLiteral = true;
1184  
-												}
1185  
-
1186  
-												break;
1187  
-											case "-":
1188  
-												if (isLiteral && !isInRange) {
1189  
-													isLiteral = false;
1190  
-													isInRange = true;
1191  
-												} else if (isInRange) {
1192  
-													isInRange = false;
1193  
-												} else if (input.charAt(l) === "]") {
1194  
-													isInRange = true;
1195  
-												} else {
1196  
-													if (option.regexdash !== (l === 2 || (l === 3 &&
1197  
-														input.charAt(1) === "^"))) {
1198  
-														warningAt("W125", line, from + l - 1, "-");
1199  
-													}
1200  
-													isLiteral = true;
1201  
-												}
1202  
-												break;
1203  
-											case "]":
1204  
-												if (isInRange && !option.regexdash) {
1205  
-													warningAt("W125", line, from + l - 1, "-");
1206  
-												}
1207  
-												break klass;
1208  
-											case "\\":
1209  
-												c = input.charAt(l);
1210  
-
1211  
-												if (c < " ") {
1212  
-													warningAt("W123", line, from + l);
1213  
-												} else if (c === "<") {
1214  
-													warningAt("W124", line, from + l, c);
1215  
-												}
1216  
-
1217  
-												l += 1;
1218  
-
1219  
-												// \w, \s and \d are never part of a character range
1220  
-												if (/[wsd]/i.test(c)) {
1221  
-													if (isInRange) {
1222  
-														warningAt("W125", line, from + l, "-");
1223  
-														isInRange = false;
1224  
-													}
1225  
-													isLiteral = false;
1226  
-												} else if (isInRange) {
1227  
-													isInRange = false;
1228  
-												} else {
1229  
-													isLiteral = true;
1230  
-												}
1231  
-												break;
1232  
-											case "/":
1233  
-												warningAt("W128", line, from + l - 1, "/");
1234  
-
1235  
-												if (isInRange) {
1236  
-													isInRange = false;
1237  
-												} else {
1238  
-													isLiteral = true;
1239  
-												}
1240  
-
1241  
-												break;
1242  
-											case "<":
1243  
-												if (isInRange) {
1244  
-													isInRange = false;
1245  
-												} else {
1246  
-													isLiteral = true;
1247  
-												}
1248  
-												break;
1249  
-											default:
1250  
-												if (isInRange) {
1251  
-													isInRange = false;
1252  
-												} else {
1253  
-													isLiteral = true;
1254  
-												}
1255  
-											}
1256  
-										} while (c);
1257  
-										break;
1258  
-									case ".":
1259  
-										if (option.regexp) {
1260  
-											warningAt("W129", line, from + l, c);
1261  
-										}
1262  
-										break;
1263  
-									case "]":
1264  
-									case "?":
1265  
-									case "{":
1266  
-									case "}":
1267  
-									case "+":
1268  
-									case "*":
1269  
-										warningAt("W125", line, from + l, c);
1270  
-									}
1271  
-
1272  
-									if (b) {
1273  
-										switch (input.charAt(l)) {
1274  
-										case "?":
1275  
-										case "+":
1276  
-										case "*":
1277  
-											l += 1;
1278  
-											if (input.charAt(l) === "?") {
1279  
-												l += 1;
1280  
-											}
1281  
-											break;
1282  
-										case "{":
1283  
-											l += 1;
1284  
-											c = input.charAt(l);
1285  
-											if (c < "0" || c > "9") {
1286  
-												warningAt("W130", line, from + l, c);
1287  
-												break; // No reason to continue checking numbers.
1288  
-											}
1289  
-											l += 1;
1290  
-											low = +c;
1291  
-											for (;;) {
1292  
-												c = input.charAt(l);
1293  
-												if (c < "0" || c > "9") {
1294  
-													break;
1295  
-												}
1296  
-												l += 1;
1297  
-												low = +c + (low * 10);
1298  
-											}
1299  
-											high = low;
1300  
-											if (c === ",") {
1301  
-												l += 1;
1302  
-												high = Infinity;
1303  
-												c = input.charAt(l);
1304  
-												if (c >= "0" && c <= "9") {
1305  
-													l += 1;
1306  
-													high = +c;
1307  
-													for (;;) {
1308  
-														c = input.charAt(l);
1309  
-														if (c < "0" || c > "9") {
1310  
-															break;
1311  
-														}
1312  
-														l += 1;
1313  
-														high = +c + (high * 10);
1314  
-													}
1315  
-												}
1316  
-											}
1317  
-											if (input.charAt(l) !== "}") {
1318  
-												warningAt("W132", line, from + l, "}", c);
1319  
-											} else {
1320  
-												l += 1;
1321  
-											}
1322  
-											if (input.charAt(l) === "?") {
1323  
-												l += 1;
1324  
-											}
1325  
-											if (low > high) {
1326  
-												warningAt("W131", line, from + l, low, high);
1327  
-											}
1328  
-										}
1329  
-									}
1330  
-								}
1331  
-								c = input.substr(0, l - 1);
1332  
-								character += l;
1333  
-								input = input.substr(l);
1334  
-								return it("(regexp)", c);
1335  
-							}
1336  
-							return it("(punctuator)", t);
1337  
-
1338  
-						// punctuator
1339  
-
1340  
-						case "#":
1341  
-							return it("(punctuator)", t);
1342  
-						default:
1343  
-							return it("(punctuator)", t);
1344  
-						}
1345  
-					}
1346  
-				}
1347  
-			}
1348  
-		};
1349  
-	}());
1350  
-
1351  
-
1352  
-	function addlabel(t, type, token) {
  499
+	function addlabel(t, type, tkn) {
1353 500
 		if (t === "hasOwnProperty") {
1354 501
 			warning("W001");
1355 502
 		}
@@ -1357,34 +504,34 @@ klass:
1357 504
 		// Define t in the current function in the current scope.
1358 505
 		if (type === "exception") {
1359 506
 			if (is_own(funct["(context)"], t)) {
1360  
-				if (funct[t] !== true && !option.node) {
1361  
-					warning("W002", nexttoken, t);
  507
+				if (funct[t] !== true && !state.option.node) {
  508
+					warning("W002", state.tokens.next, t);
1362 509
 				}
1363 510
 			}
1364 511
 		}
1365 512
 
1366 513
 		if (is_own(funct, t) && !funct["(global)"]) {
1367 514
 			if (funct[t] === true) {
1368  
-				if (option.latedef)
1369  
-					warning("W003", nexttoken, t);
  515
+				if (state.option.latedef)
  516
+					warning("W003", state.tokens.next, t);
1370 517
 			} else {
1371  
-				if (!option.shadow && type !== "exception") {
1372  
-					warning("W004", nexttoken, t);
  518
+				if (!state.option.shadow && type !== "exception") {
  519
+					warning("W004", state.tokens.next, t);
1373 520
 				}
1374 521
 			}
1375 522
 		}
1376 523
 
1377 524
 		funct[t] = type;
1378 525
 
1379  
-		if (token) {
1380  
-			funct["(tokens)"][t] = token;
  526
+		if (tkn) {
  527
+			funct["(tokens)"][t] = tkn;
1381 528
 		}
1382 529
 
1383 530
 		if (funct["(global)"]) {
1384 531
 			global[t] = funct;
1385 532
 			if (is_own(implied, t)) {
1386  
-				if (option.latedef) {
1387  
-					warning("W003", nexttoken, t);
  533
+				if (state.option.latedef) {
  534
+					warning("W003", state.tokens.next, t);
1388 535
 				}
1389 536
 
1390 537
 				delete implied[t];
@@ -1396,9 +543,9 @@ klass:
1396 543
 
1397 544
 
1398 545
 	function doOption() {
1399  
-		var nt = nexttoken;
  546
+		var nt = state.tokens.next;
1400 547
 		var o  = nt.value;
1401  
-		var quotmarkValue = option.quotmark;
  548
+		var quotmarkValue = state.option.quotmark;
1402 549
 		var predef = {};
1403 550
 		var b, obj, filter, t, tn, v, minus;
1404 551
 
@@ -1413,11 +560,11 @@ klass:
1413 560
 				membersOnly = {};
1414 561
 			}
1415 562
 			obj = membersOnly;
1416  
-			option.quotmark = false;
  563
+			state.option.quotmark = false;
1417 564
 			break;
1418 565
 		case "/*jshint":
1419 566
 		case "/*jslint":
1420  
-			obj = option;
  567
+			obj = state.option;
1421 568
 			filter = boolOptions;
1422 569
 			break;
1423 570
 		case "/*global":
@@ -1542,7 +689,7 @@ loop:
1542 689
 		}
1543 690
 
1544 691
 		if (o === "/*members") {
1545  
-			option.quotmark = quotmarkValue;
  692
+			state.option.quotmark = quotmarkValue;
1546 693
 		}
1547 694
 
1548 695
 		combine(predefined, predef);
@@ -1583,51 +730,57 @@ loop:
1583 730
 // Produce the next token. It looks for programming errors.
1584 731
 
1585 732
 	function advance(id, t) {
1586  
-		switch (token.id) {
  733
+		switch (state.tokens.curr.id) {
1587 734
 		case "(number)":
1588  
-			if (nexttoken.id === ".") {
1589  
-				warning("W005", token);
  735
+			if (state.tokens.next.id === ".") {
  736
+				warning("W005", state.tokens.curr);
1590 737
 			}
1591 738
 			break;
1592 739
 		case "-":
1593  
-			if (nexttoken.id === "-" || nexttoken.id === "--") {
  740
+			if (state.tokens.next.id === "-" || state.tokens.next.id === "--") {
1594 741
 				warning("W006");
1595 742
 			}
1596 743
 			break;
1597 744
 		case "+":
1598  
-			if (nexttoken.id === "+" || nexttoken.id === "++") {
  745
+			if (state.tokens.next.id === "+" || state.tokens.next.id === "++") {
1599 746
 				warning("W007");
1600 747
 			}
1601 748
 			break;
1602 749
 		}
1603 750
 
1604  
-		if (token.type === "(string)" || token.identifier) {
1605  
-			anonname = token.value;
  751
+		if (state.tokens.curr.type === "(string)" || state.tokens.curr.identifier) {
  752
+			anonname = state.tokens.curr.value;
1606 753
 		}
1607 754
 
1608  
-		if (id && nexttoken.id !== id) {
  755
+		if (id && state.tokens.next.id !== id) {
1609 756
 			if (t) {
1610  
-				if (nexttoken.id === "(end)") {
  757
+				if (state.tokens.next.id === "(end)") {
1611 758
 					error("E002", t, t.id);
1612 759
 				} else {
1613  
-					error("E003", nexttoken, id, t.id, t.line, nexttoken.value);
  760
+					error("E003", state.tokens.next, id, t.id, t.line, state.tokens.next.value);
1614 761
 				}
1615  
-			} else if (nexttoken.type !== "(identifier)" || nexttoken.value !== id) {
1616  
-				warning("W132", nexttoken, id, nexttoken.value);
  762
+			} else if (state.tokens.next.type !== "(identifier)" || state.tokens.next.value !== id) {
  763
+				warning("W132", state.tokens.next, id, state.tokens.next.value);
1617 764
 			}
1618 765
 		}
1619 766
 
1620  
-		prevtoken = token;
1621  
-		token = nexttoken;
  767
+		state.tokens.prev = state.tokens.curr;
  768
+		state.tokens.curr = state.tokens.next;
1622 769
 		for (;;) {
1623  
-			nexttoken = lookahead.shift() || lex.token();
1624  
-			if (nexttoken.id === "(end)" || nexttoken.id === "(error)") {
  770
+			state.tokens.next = lookahead.shift() || lex.token();
  771
+
  772
+			if (!state.tokens.next) { // No more tokens left, give up
  773
+				quit();
  774
+			}
  775
+
  776
+			if (state.tokens.next.id === "(end)" || state.tokens.next.id === "(error)") {
1625 777
 				return;
1626 778
 			}
1627  
-			if (nexttoken.type === "special") {
  779
+
  780
+			if (state.tokens.next.type === "special") {
1628 781
 				doOption();
1629 782
 			} else {
1630  
-				if (nexttoken.id !== "(endline)") {
  783
+				if (state.tokens.next.id !== "(endline)") {
1631 784
 					break;
1632 785
 				}
1633 786
 			}
@@ -1652,31 +805,33 @@ loop: