From f6338d91090ec92e34423ff236f62e4c5e64b66b Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Thu, 1 Mar 2012 22:47:31 +0000 Subject: [PATCH] Replace pegs parser with Esprima It preserves comments and text positions, enabling us to do syntax highlighting. Hopefully. --- js/App.js | 8 +- js/JavaScriptParseTree.js | 125 +- js/JavaScriptParser.js | 7 +- js/Renderer.js | 54 +- node_modules/.bin/esparse | 1 + node_modules/.bin/pegjs | 1 - node_modules/esprima/.travis.yml | 5 + node_modules/esprima/LICENSE.BSD | 19 + node_modules/esprima/README.md | 66 + .../esprima/assets/codemirror/codemirror.css | 107 + .../esprima/assets/codemirror/codemirror.js | 2817 +++ .../esprima/assets/codemirror/javascript.js | 360 + node_modules/esprima/assets/json2.js | 487 + node_modules/esprima/assets/style.css | 164 + node_modules/esprima/bin/esparse.js | 42 + node_modules/esprima/demo/checkenv.js | 42 + node_modules/esprima/demo/collector.html | 82 + node_modules/esprima/demo/collector.js | 170 + node_modules/esprima/demo/functiontrace.html | 105 + node_modules/esprima/demo/functiontrace.js | 123 + node_modules/esprima/demo/parse.css | 82 + node_modules/esprima/demo/parse.html | 190 + node_modules/esprima/demo/precedence.html | 225 + node_modules/esprima/esprima.js | 4202 ++++ node_modules/esprima/index.html | 92 + node_modules/esprima/package.json | 31 + .../esprima/test/3rdparty/Tokenizer.js | 646 + .../esprima/test/3rdparty/XMLHttpRequest.js | 509 + .../esprima/test/3rdparty/ZeParser.js | 2185 ++ .../esprima/test/3rdparty/backbone-0.5.3.js | 1158 + .../esprima/test/3rdparty/benchmark.js | 3261 +++ .../esprima/test/3rdparty/ext-core-3.0.0.js | 6579 ++++++ .../esprima/test/3rdparty/ext-core-3.1.0.js | 10255 +++++++++ .../esprima/test/3rdparty/jquery-1.6.4.js | 9046 ++++++++ .../esprima/test/3rdparty/jquery-1.7.1.js | 9266 ++++++++ .../test/3rdparty/jquery.mobile-1.0.js | 6951 ++++++ node_modules/esprima/test/3rdparty/jsdefs.js | 731 + node_modules/esprima/test/3rdparty/jslex.js | 588 + node_modules/esprima/test/3rdparty/jsparse.js | 1921 ++ .../esprima/test/3rdparty/mootools-1.3.2.js | 5952 +++++ .../esprima/test/3rdparty/mootools-1.4.1.js | 6297 +++++ .../esprima/test/3rdparty/parse-js.js | 1342 ++ .../esprima/test/3rdparty/platform.js | 891 + .../esprima/test/3rdparty/prototype-1.6.1.js | 4874 ++++ .../test/3rdparty/prototype-1.7.0.0.js | 6082 +++++ .../esprima/test/3rdparty/underscore-1.2.3.js | 981 + node_modules/esprima/test/benchmarks.html | 58 + node_modules/esprima/test/benchmarks.js | 328 + node_modules/esprima/test/compare.html | 130 + node_modules/esprima/test/compare.js | 258 + node_modules/esprima/test/compat.html | 40 + node_modules/esprima/test/compat.js | 239 + node_modules/esprima/test/index.html | 36 + node_modules/esprima/test/reflect.js | 403 + node_modules/esprima/test/run.js | 66 + node_modules/esprima/test/test.js | 19018 ++++++++++++++++ node_modules/pegjs/CHANGELOG | 146 - node_modules/pegjs/LICENSE | 22 - node_modules/pegjs/README.md | 226 - node_modules/pegjs/VERSION | 1 - node_modules/pegjs/bin/pegjs | 142 - node_modules/pegjs/examples/arithmetics.pegjs | 22 - node_modules/pegjs/examples/css.pegjs | 554 - node_modules/pegjs/examples/javascript.pegjs | 1530 -- node_modules/pegjs/examples/json.pegjs | 120 - node_modules/pegjs/lib/peg.js | 5141 ----- node_modules/pegjs/package.json | 24 - tiddlywiki5/tiddlywiki5.recipe | 4 +- 68 files changed, 109553 insertions(+), 8077 deletions(-) create mode 120000 node_modules/.bin/esparse delete mode 120000 node_modules/.bin/pegjs create mode 100644 node_modules/esprima/.travis.yml create mode 100644 node_modules/esprima/LICENSE.BSD create mode 100644 node_modules/esprima/README.md create mode 100644 node_modules/esprima/assets/codemirror/codemirror.css create mode 100644 node_modules/esprima/assets/codemirror/codemirror.js create mode 100644 node_modules/esprima/assets/codemirror/javascript.js create mode 100644 node_modules/esprima/assets/json2.js create mode 100644 node_modules/esprima/assets/style.css create mode 100755 node_modules/esprima/bin/esparse.js create mode 100644 node_modules/esprima/demo/checkenv.js create mode 100644 node_modules/esprima/demo/collector.html create mode 100644 node_modules/esprima/demo/collector.js create mode 100644 node_modules/esprima/demo/functiontrace.html create mode 100644 node_modules/esprima/demo/functiontrace.js create mode 100644 node_modules/esprima/demo/parse.css create mode 100644 node_modules/esprima/demo/parse.html create mode 100644 node_modules/esprima/demo/precedence.html create mode 100644 node_modules/esprima/esprima.js create mode 100644 node_modules/esprima/index.html create mode 100644 node_modules/esprima/package.json create mode 100644 node_modules/esprima/test/3rdparty/Tokenizer.js create mode 100644 node_modules/esprima/test/3rdparty/XMLHttpRequest.js create mode 100644 node_modules/esprima/test/3rdparty/ZeParser.js create mode 100644 node_modules/esprima/test/3rdparty/backbone-0.5.3.js create mode 100644 node_modules/esprima/test/3rdparty/benchmark.js create mode 100644 node_modules/esprima/test/3rdparty/ext-core-3.0.0.js create mode 100644 node_modules/esprima/test/3rdparty/ext-core-3.1.0.js create mode 100644 node_modules/esprima/test/3rdparty/jquery-1.6.4.js create mode 100644 node_modules/esprima/test/3rdparty/jquery-1.7.1.js create mode 100644 node_modules/esprima/test/3rdparty/jquery.mobile-1.0.js create mode 100644 node_modules/esprima/test/3rdparty/jsdefs.js create mode 100644 node_modules/esprima/test/3rdparty/jslex.js create mode 100644 node_modules/esprima/test/3rdparty/jsparse.js create mode 100644 node_modules/esprima/test/3rdparty/mootools-1.3.2.js create mode 100644 node_modules/esprima/test/3rdparty/mootools-1.4.1.js create mode 100644 node_modules/esprima/test/3rdparty/parse-js.js create mode 100644 node_modules/esprima/test/3rdparty/platform.js create mode 100644 node_modules/esprima/test/3rdparty/prototype-1.6.1.js create mode 100644 node_modules/esprima/test/3rdparty/prototype-1.7.0.0.js create mode 100644 node_modules/esprima/test/3rdparty/underscore-1.2.3.js create mode 100644 node_modules/esprima/test/benchmarks.html create mode 100644 node_modules/esprima/test/benchmarks.js create mode 100644 node_modules/esprima/test/compare.html create mode 100644 node_modules/esprima/test/compare.js create mode 100644 node_modules/esprima/test/compat.html create mode 100644 node_modules/esprima/test/compat.js create mode 100644 node_modules/esprima/test/index.html create mode 100644 node_modules/esprima/test/reflect.js create mode 100644 node_modules/esprima/test/run.js create mode 100644 node_modules/esprima/test/test.js delete mode 100644 node_modules/pegjs/CHANGELOG delete mode 100644 node_modules/pegjs/LICENSE delete mode 100644 node_modules/pegjs/README.md delete mode 100644 node_modules/pegjs/VERSION delete mode 100755 node_modules/pegjs/bin/pegjs delete mode 100644 node_modules/pegjs/examples/arithmetics.pegjs delete mode 100644 node_modules/pegjs/examples/css.pegjs delete mode 100644 node_modules/pegjs/examples/javascript.pegjs delete mode 100644 node_modules/pegjs/examples/json.pegjs delete mode 100644 node_modules/pegjs/lib/peg.js delete mode 100644 node_modules/pegjs/package.json diff --git a/js/App.js b/js/App.js index 852da1888..5aeeee32c 100644 --- a/js/App.js +++ b/js/App.js @@ -34,6 +34,8 @@ var App = function() { this.store.registerParser("image/jpeg",imageParser); this.store.registerParser("image/png",imageParser); this.store.registerParser("image/gif",imageParser); + // Set up the JavaScript parser + this.store.jsParser = new JavaScriptParser(); // Register the standard tiddler serializers and deserializers tiddlerInput.register(this.store); tiddlerOutput.register(this.store); @@ -79,12 +81,6 @@ var App = function() { this.store.addTiddler(new Tiddler(tiddlers[t])); } } - // Set up the JavaScript parser. Currently a hack; the idea is that the parsers would be loaded through a boot recipe - if(this.isBrowser) { - this.store.jsParser = new JavaScriptParser(this.store.getTiddlerText("javascript.pegjs")); - } else { - this.store.jsParser = new JavaScriptParser(require("fs").readFileSync("parsers/javascript.pegjs","utf8")); - } // Bit of a hack to set up the macros this.store.installMacro(require("./macros/echo.js").macro); this.store.installMacro(require("./macros/image.js").macro); diff --git a/js/JavaScriptParseTree.js b/js/JavaScriptParseTree.js index 2090d4bbc..28b4c7e73 100644 --- a/js/JavaScriptParseTree.js +++ b/js/JavaScriptParseTree.js @@ -18,7 +18,8 @@ This simplifies coalescing adjacent constants into a single string. /*jslint node: true */ "use strict"; -var utils = require("./Utils.js"); +var esprima = require("esprima"), + utils = require("./Utils.js"); // Create a new JavaScript tree object var JavaScriptParseTree = function(tree) { @@ -42,127 +43,7 @@ JavaScriptParseTree.prototype.toString = function() { // Compile the entire JavaScript tree object to a renderer object JavaScriptParseTree.prototype.compileJS = function() { /*jslint evil: true */ - var output = []; - if(this.tree instanceof Array) { - this.compileSubTree(output,this.tree); - } else { - this.compileNode(output,this.tree); - } - var r = output.join(""); - return {render: eval(r)}; -}; - -// Compile a subtree of the parse tree to an array of fragments of JavaScript source code -JavaScriptParseTree.prototype.compileSubTree = function(output,tree) { - for(var t=0; t BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/node_modules/esprima/README.md b/node_modules/esprima/README.md new file mode 100644 index 000000000..70031a469 --- /dev/null +++ b/node_modules/esprima/README.md @@ -0,0 +1,66 @@ +Esprima ([esprima.org](http://esprima.org)) is an educational +[ECMAScript](http://www.ecma-international.org/publications/standards/Ecma-262.htm) +(also popularly known as [JavaScript](http://en.wikipedia.org/wiki/JavaScript>JavaScript)) +parsing infrastructure for multipurpose analysis. It is also written in ECMAScript. + +Esprima can be used in a web browser: + + + +or in a Node.js application via the package manager: + + npm install esprima + +Esprima parser output is compatible with Mozilla (SpiderMonkey) +[Parser API](https://developer.mozilla.org/en/SpiderMonkey/Parser_API). + +A very simple example: + + esprima.parse('var answer=42').body[0].declarations[0].init + +produces the following object: + + { type: 'Literal', value: 42 } + +Esprima is still in the development, for now please check +[the wiki documentation](http://wiki.esprima.org). + +Since it is not comprehensive nor complete, refer to the +[issue tracker](http://issues.esprima.org) for +[known problems](http://code.google.com/p/esprima/issues/list?q=Defect) +and [future plans](http://code.google.com/p/esprima/issues/list?q=Enhancement). +Esprima is supported on [many browsers](http://code.google.com/p/esprima/wiki/BrowserCompatibility): +IE 6+, Firefox 1+, Safari 3+, Chrome 1+, and Opera 8+. + +Feedback and contribution are welcomed! Please join the +[mailing list](http://groups.google.com/group/esprima) and read the +[contribution guide](http://code.google.com/p/esprima/wiki/ContributionGuide) +for further info. + + +### License + +Copyright (C) 2012, 2011 [Ariya Hidayat](http://ariya.ofilabs.com/about) + (twitter: [@ariyahidayat](http://twitter.com/ariyahidayat)) and other contributors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/node_modules/esprima/assets/codemirror/codemirror.css b/node_modules/esprima/assets/codemirror/codemirror.css new file mode 100644 index 000000000..89d08ffbe --- /dev/null +++ b/node_modules/esprima/assets/codemirror/codemirror.css @@ -0,0 +1,107 @@ +.CodeMirror { + line-height: 1em; + font-family: monospace; +} + +.CodeMirror-scroll { + overflow: auto; + height: 300px; + /* This is needed to prevent an IE[67] bug where the scrolled content + is visible outside of the scrolling box. */ + position: relative; +} + +.CodeMirror-gutter { + position: absolute; left: 0; top: 0; + z-index: 10; + background-color: #f7f7f7; + border-right: 1px solid #eee; + min-width: 2em; + height: 100%; +} +.CodeMirror-gutter-text { + color: #aaa; + text-align: right; + padding: .4em .2em .4em .4em; + white-space: pre !important; +} +.CodeMirror-lines { + padding: .4em; +} + +.CodeMirror pre { + -moz-border-radius: 0; + -webkit-border-radius: 0; + -o-border-radius: 0; + border-radius: 0; + border-width: 0; margin: 0; padding: 0; background: transparent; + font-family: inherit; + font-size: inherit; + padding: 0; margin: 0; + white-space: pre; + word-wrap: normal; +} + +.CodeMirror-wrap pre { + word-wrap: break-word; + white-space: pre-wrap; +} +.CodeMirror-wrap .CodeMirror-scroll { + overflow-x: hidden; +} + +.CodeMirror textarea { + outline: none !important; +} + +.CodeMirror pre.CodeMirror-cursor { + z-index: 10; + position: absolute; + visibility: hidden; + border-left: 1px solid black; +} +.CodeMirror-focused pre.CodeMirror-cursor { + visibility: visible; +} + +div.CodeMirror-selected { background: #d9d9d9; } +.CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; } + +.CodeMirror-searching { + background: #ffa; + background: rgba(255, 255, 0, .4); +} + +/* Default theme */ + +.cm-s-default span.cm-keyword {color: #708;} +.cm-s-default span.cm-atom {color: #219;} +.cm-s-default span.cm-number {color: #164;} +.cm-s-default span.cm-def {color: #00f;} +.cm-s-default span.cm-variable {color: black;} +.cm-s-default span.cm-variable-2 {color: #05a;} +.cm-s-default span.cm-variable-3 {color: #085;} +.cm-s-default span.cm-property {color: black;} +.cm-s-default span.cm-operator {color: black;} +.cm-s-default span.cm-comment {color: #a50;} +.cm-s-default span.cm-string {color: #a11;} +.cm-s-default span.cm-string-2 {color: #f50;} +.cm-s-default span.cm-meta {color: #555;} +.cm-s-default span.cm-error {color: #f00;} +.cm-s-default span.cm-qualifier {color: #555;} +.cm-s-default span.cm-builtin {color: #30a;} +.cm-s-default span.cm-bracket {color: #cc7;} +.cm-s-default span.cm-tag {color: #170;} +.cm-s-default span.cm-attribute {color: #00c;} +.cm-s-default span.cm-header {color: #a0a;} +.cm-s-default span.cm-quote {color: #090;} +.cm-s-default span.cm-hr {color: #999;} +.cm-s-default span.cm-link {color: #00c;} + +span.cm-header, span.cm-strong {font-weight: bold;} +span.cm-em {font-style: italic;} +span.cm-emstrong {font-style: italic; font-weight: bold;} +span.cm-link {text-decoration: underline;} + +div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} +div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} diff --git a/node_modules/esprima/assets/codemirror/codemirror.js b/node_modules/esprima/assets/codemirror/codemirror.js new file mode 100644 index 000000000..3635eef9d --- /dev/null +++ b/node_modules/esprima/assets/codemirror/codemirror.js @@ -0,0 +1,2817 @@ +// CodeMirror version 2.21 +// +// All functions that need access to the editor's state live inside +// the CodeMirror function. Below that, at the bottom of the file, +// some utilities are defined. + +// CodeMirror is the only global var we claim +var CodeMirror = (function() { + // This is the function that produces an editor instance. It's + // closure is used to store the editor state. + function CodeMirror(place, givenOptions) { + // Determine effective options based on given values and defaults. + var options = {}, defaults = CodeMirror.defaults; + for (var opt in defaults) + if (defaults.hasOwnProperty(opt)) + options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt]; + + var targetDocument = options["document"]; + // The element in which the editor lives. + var wrapper = targetDocument.createElement("div"); + wrapper.className = "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : ""); + // This mess creates the base DOM structure for the editor. + wrapper.innerHTML = + '
' + // Wraps and hides input textarea + '
' + + '
' + + '
' + // Set to the height of the text, causes scrolling + '
' + // Moved around its parent to cover visible view + '
' + + // Provides positioning relative to (visible) text origin + '
' + + '
' + + '
 
' + // Absolutely positioned blinky cursor + '
' + // DIVs containing the selection and the actual code + '
'; + if (place.appendChild) place.appendChild(wrapper); else place(wrapper); + // I've never seen more elegant code in my life. + var inputDiv = wrapper.firstChild, input = inputDiv.firstChild, + scroller = wrapper.lastChild, code = scroller.firstChild, + mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild, + lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild, + cursor = measure.nextSibling, selectionDiv = cursor.nextSibling, + lineDiv = selectionDiv.nextSibling; + themeChanged(); + // Needed to hide big blue blinking cursor on Mobile Safari + if (ios) input.style.width = "0px"; + if (!webkit) lineSpace.draggable = true; + lineSpace.style.outline = "none"; + if (options.tabindex != null) input.tabIndex = options.tabindex; + if (!options.gutter && !options.lineNumbers) gutter.style.display = "none"; + + // Check for problem with IE innerHTML not working when we have a + // P (or similar) parent node. + try { stringWidth("x"); } + catch (e) { + if (e.message.match(/runtime/i)) + e = new Error("A CodeMirror inside a P-style element does not work in Internet Explorer. (innerHTML bug)"); + throw e; + } + + // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval. + var poll = new Delayed(), highlight = new Delayed(), blinker; + + // mode holds a mode API object. doc is the tree of Line objects, + // work an array of lines that should be parsed, and history the + // undo history (instance of History constructor). + var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), work, focused; + loadMode(); + // The selection. These are always maintained to point at valid + // positions. Inverted is used to remember that the user is + // selecting bottom-to-top. + var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false}; + // Selection-related flags. shiftSelecting obviously tracks + // whether the user is holding shift. + var shiftSelecting, lastClick, lastDoubleClick, lastScrollPos = 0, draggingText, + overwrite = false, suppressEdits = false; + // Variables used by startOperation/endOperation to track what + // happened during the operation. + var updateInput, userSelChange, changes, textChanged, selectionChanged, leaveInputAlone, + gutterDirty, callbacks; + // Current visible range (may be bigger than the view window). + var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0; + // bracketHighlighted is used to remember that a backet has been + // marked. + var bracketHighlighted; + // Tracks the maximum line length so that the horizontal scrollbar + // can be kept static when scrolling. + var maxLine = "", maxWidth, tabText = computeTabText(); + + // Initialize the content. + operation(function(){setValue(options.value || ""); updateInput = false;})(); + var history = new History(); + + // Register our event handlers. + connect(scroller, "mousedown", operation(onMouseDown)); + connect(scroller, "dblclick", operation(onDoubleClick)); + connect(lineSpace, "dragstart", onDragStart); + connect(lineSpace, "selectstart", e_preventDefault); + // Gecko browsers fire contextmenu *after* opening the menu, at + // which point we can't mess with it anymore. Context menu is + // handled in onMouseDown for Gecko. + if (!gecko) connect(scroller, "contextmenu", onContextMenu); + connect(scroller, "scroll", function() { + lastScrollPos = scroller.scrollTop; + updateDisplay([]); + if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px"; + if (options.onScroll) options.onScroll(instance); + }); + connect(window, "resize", function() {updateDisplay(true);}); + connect(input, "keyup", operation(onKeyUp)); + connect(input, "input", fastPoll); + connect(input, "keydown", operation(onKeyDown)); + connect(input, "keypress", operation(onKeyPress)); + connect(input, "focus", onFocus); + connect(input, "blur", onBlur); + + connect(scroller, "dragenter", e_stop); + connect(scroller, "dragover", e_stop); + connect(scroller, "drop", operation(onDrop)); + connect(scroller, "paste", function(){focusInput(); fastPoll();}); + connect(input, "paste", fastPoll); + connect(input, "cut", operation(function(){ + if (!options.readOnly) replaceSelection(""); + })); + + // IE throws unspecified error in certain cases, when + // trying to access activeElement before onload + var hasFocus; try { hasFocus = (targetDocument.activeElement == input); } catch(e) { } + if (hasFocus) setTimeout(onFocus, 20); + else onBlur(); + + function isLine(l) {return l >= 0 && l < doc.size;} + // The instance object that we'll return. Mostly calls out to + // local functions in the CodeMirror function. Some do some extra + // range checking and/or clipping. operation is used to wrap the + // call so that changes it makes are tracked, and the display is + // updated afterwards. + var instance = wrapper.CodeMirror = { + getValue: getValue, + setValue: operation(setValue), + getSelection: getSelection, + replaceSelection: operation(replaceSelection), + focus: function(){focusInput(); onFocus(); fastPoll();}, + setOption: function(option, value) { + var oldVal = options[option]; + options[option] = value; + if (option == "mode" || option == "indentUnit") loadMode(); + else if (option == "readOnly" && value == "nocursor") {onBlur(); input.blur();} + else if (option == "readOnly" && !value) {resetInput(true);} + else if (option == "theme") themeChanged(); + else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)(); + else if (option == "tabSize") operation(tabsChanged)(); + if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme") + updateDisplay(true); + }, + getOption: function(option) {return options[option];}, + undo: operation(undo), + redo: operation(redo), + indentLine: operation(function(n, dir) { + if (typeof dir != "string") { + if (dir == null) dir = options.smartIndent ? "smart" : "prev"; + else dir = dir ? "add" : "subtract"; + } + if (isLine(n)) indentLine(n, dir); + }), + indentSelection: operation(indentSelected), + historySize: function() {return {undo: history.done.length, redo: history.undone.length};}, + clearHistory: function() {history = new History();}, + matchBrackets: operation(function(){matchBrackets(true);}), + getTokenAt: operation(function(pos) { + pos = clipPos(pos); + return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), pos.ch); + }), + getStateAfter: function(line) { + line = clipLine(line == null ? doc.size - 1: line); + return getStateBefore(line + 1); + }, + cursorCoords: function(start){ + if (start == null) start = sel.inverted; + return pageCoords(start ? sel.from : sel.to); + }, + charCoords: function(pos){return pageCoords(clipPos(pos));}, + coordsChar: function(coords) { + var off = eltOffset(lineSpace); + return coordsChar(coords.x - off.left, coords.y - off.top); + }, + markText: operation(markText), + setBookmark: setBookmark, + setMarker: operation(addGutterMarker), + clearMarker: operation(removeGutterMarker), + setLineClass: operation(setLineClass), + hideLine: operation(function(h) {return setLineHidden(h, true);}), + showLine: operation(function(h) {return setLineHidden(h, false);}), + onDeleteLine: function(line, f) { + if (typeof line == "number") { + if (!isLine(line)) return null; + line = getLine(line); + } + (line.handlers || (line.handlers = [])).push(f); + return line; + }, + lineInfo: lineInfo, + addWidget: function(pos, node, scroll, vert, horiz) { + pos = localCoords(clipPos(pos)); + var top = pos.yBot, left = pos.x; + node.style.position = "absolute"; + code.appendChild(node); + if (vert == "over") top = pos.y; + else if (vert == "near") { + var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()), + hspace = Math.max(code.clientWidth, lineSpace.clientWidth) - paddingLeft(); + if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight) + top = pos.y - node.offsetHeight; + if (left + node.offsetWidth > hspace) + left = hspace - node.offsetWidth; + } + node.style.top = (top + paddingTop()) + "px"; + node.style.left = node.style.right = ""; + if (horiz == "right") { + left = code.clientWidth - node.offsetWidth; + node.style.right = "0px"; + } else { + if (horiz == "left") left = 0; + else if (horiz == "middle") left = (code.clientWidth - node.offsetWidth) / 2; + node.style.left = (left + paddingLeft()) + "px"; + } + if (scroll) + scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight); + }, + + lineCount: function() {return doc.size;}, + clipPos: clipPos, + getCursor: function(start) { + if (start == null) start = sel.inverted; + return copyPos(start ? sel.from : sel.to); + }, + somethingSelected: function() {return !posEq(sel.from, sel.to);}, + setCursor: operation(function(line, ch, user) { + if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch, user); + else setCursor(line, ch, user); + }), + setSelection: operation(function(from, to, user) { + (user ? setSelectionUser : setSelection)(clipPos(from), clipPos(to || from)); + }), + getLine: function(line) {if (isLine(line)) return getLine(line).text;}, + getLineHandle: function(line) {if (isLine(line)) return getLine(line);}, + setLine: operation(function(line, text) { + if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length}); + }), + removeLine: operation(function(line) { + if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0})); + }), + replaceRange: operation(replaceRange), + getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));}, + + execCommand: function(cmd) {return commands[cmd](instance);}, + // Stuff used by commands, probably not much use to outside code. + moveH: operation(moveH), + deleteH: operation(deleteH), + moveV: operation(moveV), + toggleOverwrite: function() {overwrite = !overwrite;}, + + posFromIndex: function(off) { + var lineNo = 0, ch; + doc.iter(0, doc.size, function(line) { + var sz = line.text.length + 1; + if (sz > off) { ch = off; return true; } + off -= sz; + ++lineNo; + }); + return clipPos({line: lineNo, ch: ch}); + }, + indexFromPos: function (coords) { + if (coords.line < 0 || coords.ch < 0) return 0; + var index = coords.ch; + doc.iter(0, coords.line, function (line) { + index += line.text.length + 1; + }); + return index; + }, + scrollTo: function(x, y) { + if (x != null) scroller.scrollTop = x; + if (y != null) scroller.scrollLeft = y; + updateDisplay([]); + }, + + operation: function(f){return operation(f)();}, + refresh: function(){ + updateDisplay(true); + if (scroller.scrollHeight > lastScrollPos) + scroller.scrollTop = lastScrollPos; + }, + getInputField: function(){return input;}, + getWrapperElement: function(){return wrapper;}, + getScrollerElement: function(){return scroller;}, + getGutterElement: function(){return gutter;} + }; + + function getLine(n) { return getLineAt(doc, n); } + function updateLineHeight(line, height) { + gutterDirty = true; + var diff = height - line.height; + for (var n = line; n; n = n.parent) n.height += diff; + } + + function setValue(code) { + var top = {line: 0, ch: 0}; + updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length}, + splitLines(code), top, top); + updateInput = true; + } + function getValue(code) { + var text = []; + doc.iter(0, doc.size, function(line) { text.push(line.text); }); + return text.join("\n"); + } + + function onMouseDown(e) { + setShift(e_prop(e, "shiftKey")); + // Check whether this is a click in a widget + for (var n = e_target(e); n != wrapper; n = n.parentNode) + if (n.parentNode == code && n != mover) return; + + // See if this is a click in the gutter + for (var n = e_target(e); n != wrapper; n = n.parentNode) + if (n.parentNode == gutterText) { + if (options.onGutterClick) + options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e); + return e_preventDefault(e); + } + + var start = posFromMouse(e); + + switch (e_button(e)) { + case 3: + if (gecko && !mac) onContextMenu(e); + return; + case 2: + if (start) setCursor(start.line, start.ch, true); + return; + } + // For button 1, if it was clicked inside the editor + // (posFromMouse returning non-null), we have to adjust the + // selection. + if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;} + + if (!focused) onFocus(); + + var now = +new Date; + if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) { + e_preventDefault(e); + setTimeout(focusInput, 20); + return selectLine(start.line); + } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) { + lastDoubleClick = {time: now, pos: start}; + e_preventDefault(e); + return selectWordAt(start); + } else { lastClick = {time: now, pos: start}; } + + var last = start, going; + if (dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) && + !posLess(start, sel.from) && !posLess(sel.to, start)) { + // Let the drag handler handle this. + if (webkit) lineSpace.draggable = true; + var up = connect(targetDocument, "mouseup", operation(function(e2) { + if (webkit) lineSpace.draggable = false; + draggingText = false; + up(); + if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { + e_preventDefault(e2); + setCursor(start.line, start.ch, true); + focusInput(); + } + }), true); + draggingText = true; + return; + } + e_preventDefault(e); + setCursor(start.line, start.ch, true); + + function extend(e) { + var cur = posFromMouse(e, true); + if (cur && !posEq(cur, last)) { + if (!focused) onFocus(); + last = cur; + setSelectionUser(start, cur); + updateInput = false; + var visible = visibleLines(); + if (cur.line >= visible.to || cur.line < visible.from) + going = setTimeout(operation(function(){extend(e);}), 150); + } + } + + var move = connect(targetDocument, "mousemove", operation(function(e) { + clearTimeout(going); + e_preventDefault(e); + extend(e); + }), true); + var up = connect(targetDocument, "mouseup", operation(function(e) { + clearTimeout(going); + var cur = posFromMouse(e); + if (cur) setSelectionUser(start, cur); + e_preventDefault(e); + focusInput(); + updateInput = true; + move(); up(); + }), true); + } + function onDoubleClick(e) { + for (var n = e_target(e); n != wrapper; n = n.parentNode) + if (n.parentNode == gutterText) return e_preventDefault(e); + var start = posFromMouse(e); + if (!start) return; + lastDoubleClick = {time: +new Date, pos: start}; + e_preventDefault(e); + selectWordAt(start); + } + function onDrop(e) { + e.preventDefault(); + var pos = posFromMouse(e, true), files = e.dataTransfer.files; + if (!pos || options.readOnly) return; + if (files && files.length && window.FileReader && window.File) { + function loadFile(file, i) { + var reader = new FileReader; + reader.onload = function() { + text[i] = reader.result; + if (++read == n) { + pos = clipPos(pos); + operation(function() { + var end = replaceRange(text.join(""), pos, pos); + setSelectionUser(pos, end); + })(); + } + }; + reader.readAsText(file); + } + var n = files.length, text = Array(n), read = 0; + for (var i = 0; i < n; ++i) loadFile(files[i], i); + } + else { + try { + var text = e.dataTransfer.getData("Text"); + if (text) { + var curFrom = sel.from, curTo = sel.to; + setSelectionUser(pos, pos); + if (draggingText) replaceRange("", curFrom, curTo); + replaceSelection(text); + focusInput(); + } + } + catch(e){} + } + } + function onDragStart(e) { + var txt = getSelection(); + // This will reset escapeElement + htmlEscape(txt); + e.dataTransfer.setDragImage(escapeElement, 0, 0); + e.dataTransfer.setData("Text", txt); + } + function handleKeyBinding(e) { + var name = keyNames[e_prop(e, "keyCode")], next = keyMap[options.keyMap].auto, bound, dropShift; + function handleNext() { + return next.call ? next.call(null, instance) : next; + } + if (name == null || e.altGraphKey) { + if (next) options.keyMap = handleNext(); + return null; + } + if (e_prop(e, "altKey")) name = "Alt-" + name; + if (e_prop(e, "ctrlKey")) name = "Ctrl-" + name; + if (e_prop(e, "metaKey")) name = "Cmd-" + name; + if (e_prop(e, "shiftKey") && + (bound = lookupKey("Shift-" + name, options.extraKeys, options.keyMap))) { + dropShift = true; + } else { + bound = lookupKey(name, options.extraKeys, options.keyMap); + } + if (typeof bound == "string") { + if (commands.propertyIsEnumerable(bound)) bound = commands[bound]; + else bound = null; + } + if (next && (bound || !isModifierKey(e))) options.keyMap = handleNext(); + if (!bound) return false; + var prevShift = shiftSelecting; + try { + if (options.readOnly) suppressEdits = true; + if (dropShift) shiftSelecting = null; + bound(instance); + } finally { + shiftSelecting = prevShift; + suppressEdits = false; + } + e_preventDefault(e); + return true; + } + var lastStoppedKey = null; + function onKeyDown(e) { + if (!focused) onFocus(); + if (ie && e.keyCode == 27) { e.returnValue = false; } + if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; + var code = e_prop(e, "keyCode"); + // IE does strange things with escape. + setShift(code == 16 || e_prop(e, "shiftKey")); + // First give onKeyEvent option a chance to handle this. + var handled = handleKeyBinding(e); + if (window.opera) { + lastStoppedKey = handled ? code : null; + // Opera has no cut event... we try to at least catch the key combo + if (!handled && code == 88 && e_prop(e, mac ? "metaKey" : "ctrlKey")) + replaceSelection(""); + } + } + function onKeyPress(e) { + var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode"); + if (window.opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} + if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; + if (window.opera && !e.which && handleKeyBinding(e)) return; + if (options.electricChars && mode.electricChars && options.smartIndent && !options.readOnly) { + var ch = String.fromCharCode(charCode == null ? keyCode : charCode); + if (mode.electricChars.indexOf(ch) > -1) + setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75); + } + fastPoll(); + } + function onKeyUp(e) { + if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; + if (e_prop(e, "keyCode") == 16) shiftSelecting = null; + } + + function onFocus() { + if (options.readOnly == "nocursor") return; + if (!focused) { + if (options.onFocus) options.onFocus(instance); + focused = true; + if (wrapper.className.search(/\bCodeMirror-focused\b/) == -1) + wrapper.className += " CodeMirror-focused"; + if (!leaveInputAlone) resetInput(true); + } + slowPoll(); + restartBlink(); + } + function onBlur() { + if (focused) { + if (options.onBlur) options.onBlur(instance); + focused = false; + if (bracketHighlighted) + operation(function(){ + if (bracketHighlighted) { bracketHighlighted(); bracketHighlighted = null; } + })(); + wrapper.className = wrapper.className.replace(" CodeMirror-focused", ""); + } + clearInterval(blinker); + setTimeout(function() {if (!focused) shiftSelecting = null;}, 150); + } + + // Replace the range from from to to by the strings in newText. + // Afterwards, set the selection to selFrom, selTo. + function updateLines(from, to, newText, selFrom, selTo) { + if (suppressEdits) return; + if (history) { + var old = []; + doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); }); + history.addChange(from.line, newText.length, old); + while (history.done.length > options.undoDepth) history.done.shift(); + } + updateLinesNoUndo(from, to, newText, selFrom, selTo); + } + function unredoHelper(from, to, dir) { + var set = from.pop(), len = set ? set.length : 0, out = []; + for (var i = dir > 0 ? 0 : len - 1, e = dir > 0 ? len : -1; i != e; i += dir) { + var change = set[i]; + var replaced = [], end = change.start + change.added; + doc.iter(change.start, end, function(line) { replaced.push(line.text); }); + out.push({start: change.start, added: change.old.length, old: replaced}); + var pos = clipPos({line: change.start + change.old.length - 1, + ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])}); + updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos); + } + updateInput = true; + to.push(out); + } + function undo() {unredoHelper(history.done, history.undone, -1);} + function redo() {unredoHelper(history.undone, history.done, 1);} + + function updateLinesNoUndo(from, to, newText, selFrom, selTo) { + if (suppressEdits) return; + var recomputeMaxLength = false, maxLineLength = maxLine.length; + if (!options.lineWrapping) + doc.iter(from.line, to.line, function(line) { + if (line.text.length == maxLineLength) {recomputeMaxLength = true; return true;} + }); + if (from.line != to.line || newText.length > 1) gutterDirty = true; + + var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line); + // First adjust the line structure, taking some care to leave highlighting intact. + if (from.ch == 0 && to.ch == 0 && newText[newText.length - 1] == "") { + // This is a whole-line replace. Treated specially to make + // sure line objects move the way they are supposed to. + var added = [], prevLine = null; + if (from.line) { + prevLine = getLine(from.line - 1); + prevLine.fixMarkEnds(lastLine); + } else lastLine.fixMarkStarts(); + for (var i = 0, e = newText.length - 1; i < e; ++i) + added.push(Line.inheritMarks(newText[i], prevLine)); + if (nlines) doc.remove(from.line, nlines, callbacks); + if (added.length) doc.insert(from.line, added); + } else if (firstLine == lastLine) { + if (newText.length == 1) + firstLine.replace(from.ch, to.ch, newText[0]); + else { + lastLine = firstLine.split(to.ch, newText[newText.length-1]); + firstLine.replace(from.ch, null, newText[0]); + firstLine.fixMarkEnds(lastLine); + var added = []; + for (var i = 1, e = newText.length - 1; i < e; ++i) + added.push(Line.inheritMarks(newText[i], firstLine)); + added.push(lastLine); + doc.insert(from.line + 1, added); + } + } else if (newText.length == 1) { + firstLine.replace(from.ch, null, newText[0]); + lastLine.replace(null, to.ch, ""); + firstLine.append(lastLine); + doc.remove(from.line + 1, nlines, callbacks); + } else { + var added = []; + firstLine.replace(from.ch, null, newText[0]); + lastLine.replace(null, to.ch, newText[newText.length-1]); + firstLine.fixMarkEnds(lastLine); + for (var i = 1, e = newText.length - 1; i < e; ++i) + added.push(Line.inheritMarks(newText[i], firstLine)); + if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks); + doc.insert(from.line + 1, added); + } + if (options.lineWrapping) { + var perLine = scroller.clientWidth / charWidth() - 3; + doc.iter(from.line, from.line + newText.length, function(line) { + if (line.hidden) return; + var guess = Math.ceil(line.text.length / perLine) || 1; + if (guess != line.height) updateLineHeight(line, guess); + }); + } else { + doc.iter(from.line, i + newText.length, function(line) { + var l = line.text; + if (l.length > maxLineLength) { + maxLine = l; maxLineLength = l.length; maxWidth = null; + recomputeMaxLength = false; + } + }); + if (recomputeMaxLength) { + maxLineLength = 0; maxLine = ""; maxWidth = null; + doc.iter(0, doc.size, function(line) { + var l = line.text; + if (l.length > maxLineLength) { + maxLineLength = l.length; maxLine = l; + } + }); + } + } + + // Add these lines to the work array, so that they will be + // highlighted. Adjust work lines if lines were added/removed. + var newWork = [], lendiff = newText.length - nlines - 1; + for (var i = 0, l = work.length; i < l; ++i) { + var task = work[i]; + if (task < from.line) newWork.push(task); + else if (task > to.line) newWork.push(task + lendiff); + } + var hlEnd = from.line + Math.min(newText.length, 500); + highlightLines(from.line, hlEnd); + newWork.push(hlEnd); + work = newWork; + startWorker(100); + // Remember that these lines changed, for updating the display + changes.push({from: from.line, to: to.line + 1, diff: lendiff}); + var changeObj = {from: from, to: to, text: newText}; + if (textChanged) { + for (var cur = textChanged; cur.next; cur = cur.next) {} + cur.next = changeObj; + } else textChanged = changeObj; + + // Update the selection + function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;} + setSelection(selFrom, selTo, updateLine(sel.from.line), updateLine(sel.to.line)); + + // Make sure the scroll-size div has the correct height. + if (scroller.clientHeight) + code.style.height = (doc.height * textHeight() + 2 * paddingTop()) + "px"; + } + + function replaceRange(code, from, to) { + from = clipPos(from); + if (!to) to = from; else to = clipPos(to); + code = splitLines(code); + function adjustPos(pos) { + if (posLess(pos, from)) return pos; + if (!posLess(to, pos)) return end; + var line = pos.line + code.length - (to.line - from.line) - 1; + var ch = pos.ch; + if (pos.line == to.line) + ch += code[code.length-1].length - (to.ch - (to.line == from.line ? from.ch : 0)); + return {line: line, ch: ch}; + } + var end; + replaceRange1(code, from, to, function(end1) { + end = end1; + return {from: adjustPos(sel.from), to: adjustPos(sel.to)}; + }); + return end; + } + function replaceSelection(code, collapse) { + replaceRange1(splitLines(code), sel.from, sel.to, function(end) { + if (collapse == "end") return {from: end, to: end}; + else if (collapse == "start") return {from: sel.from, to: sel.from}; + else return {from: sel.from, to: end}; + }); + } + function replaceRange1(code, from, to, computeSel) { + var endch = code.length == 1 ? code[0].length + from.ch : code[code.length-1].length; + var newSel = computeSel({line: from.line + code.length - 1, ch: endch}); + updateLines(from, to, code, newSel.from, newSel.to); + } + + function getRange(from, to) { + var l1 = from.line, l2 = to.line; + if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch); + var code = [getLine(l1).text.slice(from.ch)]; + doc.iter(l1 + 1, l2, function(line) { code.push(line.text); }); + code.push(getLine(l2).text.slice(0, to.ch)); + return code.join("\n"); + } + function getSelection() { + return getRange(sel.from, sel.to); + } + + var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll + function slowPoll() { + if (pollingFast) return; + poll.set(options.pollInterval, function() { + startOperation(); + readInput(); + if (focused) slowPoll(); + endOperation(); + }); + } + function fastPoll() { + var missed = false; + pollingFast = true; + function p() { + startOperation(); + var changed = readInput(); + if (!changed && !missed) {missed = true; poll.set(60, p);} + else {pollingFast = false; slowPoll();} + endOperation(); + } + poll.set(20, p); + } + + // Previnput is a hack to work with IME. If we reset the textarea + // on every change, that breaks IME. So we look for changes + // compared to the previous content instead. (Modern browsers have + // events that indicate IME taking place, but these are not widely + // supported or compatible enough yet to rely on.) + var prevInput = ""; + function readInput() { + if (leaveInputAlone || !focused || hasSelection(input) || options.readOnly) return false; + var text = input.value; + if (text == prevInput) return false; + shiftSelecting = null; + var same = 0, l = Math.min(prevInput.length, text.length); + while (same < l && prevInput[same] == text[same]) ++same; + if (same < prevInput.length) + sel.from = {line: sel.from.line, ch: sel.from.ch - (prevInput.length - same)}; + else if (overwrite && posEq(sel.from, sel.to)) + sel.to = {line: sel.to.line, ch: Math.min(getLine(sel.to.line).text.length, sel.to.ch + (text.length - same))}; + replaceSelection(text.slice(same), "end"); + prevInput = text; + return true; + } + function resetInput(user) { + if (!posEq(sel.from, sel.to)) { + prevInput = ""; + input.value = getSelection(); + input.select(); + } else if (user) prevInput = input.value = ""; + } + + function focusInput() { + if (options.readOnly != "nocursor") input.focus(); + } + + function scrollEditorIntoView() { + if (!cursor.getBoundingClientRect) return; + var rect = cursor.getBoundingClientRect(); + // IE returns bogus coordinates when the instance sits inside of an iframe and the cursor is hidden + if (ie && rect.top == rect.bottom) return; + var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight); + if (rect.top < 0 || rect.bottom > winH) cursor.scrollIntoView(); + } + function scrollCursorIntoView() { + var cursor = localCoords(sel.inverted ? sel.from : sel.to); + var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x; + return scrollIntoView(x, cursor.y, x, cursor.yBot); + } + function scrollIntoView(x1, y1, x2, y2) { + var pl = paddingLeft(), pt = paddingTop(), lh = textHeight(); + y1 += pt; y2 += pt; x1 += pl; x2 += pl; + var screen = scroller.clientHeight, screentop = scroller.scrollTop, scrolled = false, result = true; + if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1 - 2*lh); scrolled = true;} + else if (y2 > screentop + screen) {scroller.scrollTop = y2 + lh - screen; scrolled = true;} + + var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft; + var gutterw = options.fixedGutter ? gutter.clientWidth : 0; + if (x1 < screenleft + gutterw) { + if (x1 < 50) x1 = 0; + scroller.scrollLeft = Math.max(0, x1 - 10 - gutterw); + scrolled = true; + } + else if (x2 > screenw + screenleft - 3) { + scroller.scrollLeft = x2 + 10 - screenw; + scrolled = true; + if (x2 > code.clientWidth) result = false; + } + if (scrolled && options.onScroll) options.onScroll(instance); + return result; + } + + function visibleLines() { + var lh = textHeight(), top = scroller.scrollTop - paddingTop(); + var from_height = Math.max(0, Math.floor(top / lh)); + var to_height = Math.ceil((top + scroller.clientHeight) / lh); + return {from: lineAtHeight(doc, from_height), + to: lineAtHeight(doc, to_height)}; + } + // Uses a set of changes plus the current scroll position to + // determine which DOM updates have to be made, and makes the + // updates. + function updateDisplay(changes, suppressCallback) { + if (!scroller.clientWidth) { + showingFrom = showingTo = displayOffset = 0; + return; + } + // Compute the new visible window + var visible = visibleLines(); + // Bail out if the visible area is already rendered and nothing changed. + if (changes !== true && changes.length == 0 && visible.from > showingFrom && visible.to < showingTo) return; + var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100); + if (showingFrom < from && from - showingFrom < 20) from = showingFrom; + if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo); + + // Create a range of theoretically intact lines, and punch holes + // in that using the change info. + var intact = changes === true ? [] : + computeIntact([{from: showingFrom, to: showingTo, domStart: 0}], changes); + // Clip off the parts that won't be visible + var intactLines = 0; + for (var i = 0; i < intact.length; ++i) { + var range = intact[i]; + if (range.from < from) {range.domStart += (from - range.from); range.from = from;} + if (range.to > to) range.to = to; + if (range.from >= range.to) intact.splice(i--, 1); + else intactLines += range.to - range.from; + } + if (intactLines == to - from) return; + intact.sort(function(a, b) {return a.domStart - b.domStart;}); + + var th = textHeight(), gutterDisplay = gutter.style.display; + lineDiv.style.display = "none"; + patchDisplay(from, to, intact); + lineDiv.style.display = gutter.style.display = ""; + + // Position the mover div to align with the lines it's supposed + // to be showing (which will cover the visible display) + var different = from != showingFrom || to != showingTo || lastSizeC != scroller.clientHeight + th; + // This is just a bogus formula that detects when the editor is + // resized or the font size changes. + if (different) lastSizeC = scroller.clientHeight + th; + showingFrom = from; showingTo = to; + displayOffset = heightAtLine(doc, from); + mover.style.top = (displayOffset * th) + "px"; + if (scroller.clientHeight) + code.style.height = (doc.height * th + 2 * paddingTop()) + "px"; + + // Since this is all rather error prone, it is honoured with the + // only assertion in the whole file. + if (lineDiv.childNodes.length != showingTo - showingFrom) + throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) + + " nodes=" + lineDiv.childNodes.length); + + if (options.lineWrapping) { + maxWidth = scroller.clientWidth; + var curNode = lineDiv.firstChild, heightChanged = false; + doc.iter(showingFrom, showingTo, function(line) { + if (!line.hidden) { + var height = Math.round(curNode.offsetHeight / th) || 1; + if (line.height != height) { + updateLineHeight(line, height); + gutterDirty = heightChanged = true; + } + } + curNode = curNode.nextSibling; + }); + if (heightChanged) + code.style.height = (doc.height * th + 2 * paddingTop()) + "px"; + } else { + if (maxWidth == null) maxWidth = stringWidth(maxLine); + if (maxWidth > scroller.clientWidth) { + lineSpace.style.width = maxWidth + "px"; + // Needed to prevent odd wrapping/hiding of widgets placed in here. + code.style.width = ""; + code.style.width = scroller.scrollWidth + "px"; + } else { + lineSpace.style.width = code.style.width = ""; + } + } + gutter.style.display = gutterDisplay; + if (different || gutterDirty) updateGutter(); + updateSelection(); + if (!suppressCallback && options.onUpdate) options.onUpdate(instance); + return true; + } + + function computeIntact(intact, changes) { + for (var i = 0, l = changes.length || 0; i < l; ++i) { + var change = changes[i], intact2 = [], diff = change.diff || 0; + for (var j = 0, l2 = intact.length; j < l2; ++j) { + var range = intact[j]; + if (change.to <= range.from && change.diff) + intact2.push({from: range.from + diff, to: range.to + diff, + domStart: range.domStart}); + else if (change.to <= range.from || change.from >= range.to) + intact2.push(range); + else { + if (change.from > range.from) + intact2.push({from: range.from, to: change.from, domStart: range.domStart}); + if (change.to < range.to) + intact2.push({from: change.to + diff, to: range.to + diff, + domStart: range.domStart + (change.to - range.from)}); + } + } + intact = intact2; + } + return intact; + } + + function patchDisplay(from, to, intact) { + // The first pass removes the DOM nodes that aren't intact. + if (!intact.length) lineDiv.innerHTML = ""; + else { + function killNode(node) { + var tmp = node.nextSibling; + node.parentNode.removeChild(node); + return tmp; + } + var domPos = 0, curNode = lineDiv.firstChild, n; + for (var i = 0; i < intact.length; ++i) { + var cur = intact[i]; + while (cur.domStart > domPos) {curNode = killNode(curNode); domPos++;} + for (var j = 0, e = cur.to - cur.from; j < e; ++j) {curNode = curNode.nextSibling; domPos++;} + } + while (curNode) curNode = killNode(curNode); + } + // This pass fills in the lines that actually changed. + var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from; + var scratch = targetDocument.createElement("div"), newElt; + doc.iter(from, to, function(line) { + if (nextIntact && nextIntact.to == j) nextIntact = intact.shift(); + if (!nextIntact || nextIntact.from > j) { + if (line.hidden) var html = scratch.innerHTML = "
";
+          else {
+            var html = '
' + line.getHTML(tabText) + '
'; + // Kludge to make sure the styled element lies behind the selection (by z-index) + if (line.className) + html = '
 
' + html + "
"; + } + scratch.innerHTML = html; + lineDiv.insertBefore(scratch.firstChild, curNode); + } else { + curNode = curNode.nextSibling; + } + ++j; + }); + } + + function updateGutter() { + if (!options.gutter && !options.lineNumbers) return; + var hText = mover.offsetHeight, hEditor = scroller.clientHeight; + gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px"; + var html = [], i = showingFrom; + doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) { + if (line.hidden) { + html.push("
");
+        } else {
+          var marker = line.gutterMarker;
+          var text = options.lineNumbers ? i + options.firstLineNumber : null;
+          if (marker && marker.text)
+            text = marker.text.replace("%N%", text != null ? text : "");
+          else if (text == null)
+            text = "\u00a0";
+          html.push((marker && marker.style ? '
' : "
"), text);
+          for (var j = 1; j < line.height; ++j) html.push("
 "); + html.push("
"); + } + ++i; + }); + gutter.style.display = "none"; + gutterText.innerHTML = html.join(""); + var minwidth = String(doc.size).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = ""; + while (val.length + pad.length < minwidth) pad += "\u00a0"; + if (pad) firstNode.insertBefore(targetDocument.createTextNode(pad), firstNode.firstChild); + gutter.style.display = ""; + lineSpace.style.marginLeft = gutter.offsetWidth + "px"; + gutterDirty = false; + } + function updateSelection() { + var collapsed = posEq(sel.from, sel.to); + var fromPos = localCoords(sel.from, true); + var toPos = collapsed ? fromPos : localCoords(sel.to, true); + var headPos = sel.inverted ? fromPos : toPos, th = textHeight(); + var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv); + inputDiv.style.top = Math.max(0, Math.min(scroller.offsetHeight, headPos.y + lineOff.top - wrapOff.top)) + "px"; + inputDiv.style.left = Math.max(0, Math.min(scroller.offsetWidth, headPos.x + lineOff.left - wrapOff.left)) + "px"; + if (collapsed) { + cursor.style.top = headPos.y + "px"; + cursor.style.left = (options.lineWrapping ? Math.min(headPos.x, lineSpace.offsetWidth) : headPos.x) + "px"; + cursor.style.display = ""; + selectionDiv.style.display = "none"; + } else { + var sameLine = fromPos.y == toPos.y, html = ""; + function add(left, top, right, height) { + html += '
'; + } + if (sel.from.ch && fromPos.y >= 0) { + var right = sameLine ? lineSpace.clientWidth - toPos.x : 0; + add(fromPos.x, fromPos.y, right, th); + } + var middleStart = Math.max(0, fromPos.y + (sel.from.ch ? th : 0)); + var middleHeight = Math.min(toPos.y, lineSpace.clientHeight) - middleStart; + if (middleHeight > 0.2 * th) + add(0, middleStart, 0, middleHeight); + if ((!sameLine || !sel.from.ch) && toPos.y < lineSpace.clientHeight - .5 * th) + add(0, toPos.y, lineSpace.clientWidth - toPos.x, th); + selectionDiv.innerHTML = html; + cursor.style.display = "none"; + selectionDiv.style.display = ""; + } + } + + function setShift(val) { + if (val) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from); + else shiftSelecting = null; + } + function setSelectionUser(from, to) { + var sh = shiftSelecting && clipPos(shiftSelecting); + if (sh) { + if (posLess(sh, from)) from = sh; + else if (posLess(to, sh)) to = sh; + } + setSelection(from, to); + userSelChange = true; + } + // Update the selection. Last two args are only used by + // updateLines, since they have to be expressed in the line + // numbers before the update. + function setSelection(from, to, oldFrom, oldTo) { + goalColumn = null; + if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;} + if (posEq(sel.from, from) && posEq(sel.to, to)) return; + if (posLess(to, from)) {var tmp = to; to = from; from = tmp;} + + // Skip over hidden lines. + if (from.line != oldFrom) from = skipHidden(from, oldFrom, sel.from.ch); + if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch); + + if (posEq(from, to)) sel.inverted = false; + else if (posEq(from, sel.to)) sel.inverted = false; + else if (posEq(to, sel.from)) sel.inverted = true; + + sel.from = from; sel.to = to; + selectionChanged = true; + } + function skipHidden(pos, oldLine, oldCh) { + function getNonHidden(dir) { + var lNo = pos.line + dir, end = dir == 1 ? doc.size : -1; + while (lNo != end) { + var line = getLine(lNo); + if (!line.hidden) { + var ch = pos.ch; + if (ch > oldCh || ch > line.text.length) ch = line.text.length; + return {line: lNo, ch: ch}; + } + lNo += dir; + } + } + var line = getLine(pos.line); + if (!line.hidden) return pos; + if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1); + else return getNonHidden(-1) || getNonHidden(1); + } + function setCursor(line, ch, user) { + var pos = clipPos({line: line, ch: ch || 0}); + (user ? setSelectionUser : setSelection)(pos, pos); + } + + function clipLine(n) {return Math.max(0, Math.min(n, doc.size-1));} + function clipPos(pos) { + if (pos.line < 0) return {line: 0, ch: 0}; + if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc.size-1).text.length}; + var ch = pos.ch, linelen = getLine(pos.line).text.length; + if (ch == null || ch > linelen) return {line: pos.line, ch: linelen}; + else if (ch < 0) return {line: pos.line, ch: 0}; + else return pos; + } + + function findPosH(dir, unit) { + var end = sel.inverted ? sel.from : sel.to, line = end.line, ch = end.ch; + var lineObj = getLine(line); + function findNextLine() { + for (var l = line + dir, e = dir < 0 ? -1 : doc.size; l != e; l += dir) { + var lo = getLine(l); + if (!lo.hidden) { line = l; lineObj = lo; return true; } + } + } + function moveOnce(boundToLine) { + if (ch == (dir < 0 ? 0 : lineObj.text.length)) { + if (!boundToLine && findNextLine()) ch = dir < 0 ? lineObj.text.length : 0; + else return false; + } else ch += dir; + return true; + } + if (unit == "char") moveOnce(); + else if (unit == "column") moveOnce(true); + else if (unit == "word") { + var sawWord = false; + for (;;) { + if (dir < 0) if (!moveOnce()) break; + if (isWordChar(lineObj.text.charAt(ch))) sawWord = true; + else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;} + if (dir > 0) if (!moveOnce()) break; + } + } + return {line: line, ch: ch}; + } + function moveH(dir, unit) { + var pos = dir < 0 ? sel.from : sel.to; + if (shiftSelecting || posEq(sel.from, sel.to)) pos = findPosH(dir, unit); + setCursor(pos.line, pos.ch, true); + } + function deleteH(dir, unit) { + if (!posEq(sel.from, sel.to)) replaceRange("", sel.from, sel.to); + else if (dir < 0) replaceRange("", findPosH(dir, unit), sel.to); + else replaceRange("", sel.from, findPosH(dir, unit)); + userSelChange = true; + } + var goalColumn = null; + function moveV(dir, unit) { + var dist = 0, pos = localCoords(sel.inverted ? sel.from : sel.to, true); + if (goalColumn != null) pos.x = goalColumn; + if (unit == "page") dist = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight); + else if (unit == "line") dist = textHeight(); + var target = coordsChar(pos.x, pos.y + dist * dir + 2); + setCursor(target.line, target.ch, true); + goalColumn = pos.x; + } + + function selectWordAt(pos) { + var line = getLine(pos.line).text; + var start = pos.ch, end = pos.ch; + while (start > 0 && isWordChar(line.charAt(start - 1))) --start; + while (end < line.length && isWordChar(line.charAt(end))) ++end; + setSelectionUser({line: pos.line, ch: start}, {line: pos.line, ch: end}); + } + function selectLine(line) { + setSelectionUser({line: line, ch: 0}, {line: line, ch: getLine(line).text.length}); + } + function indentSelected(mode) { + if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode); + var e = sel.to.line - (sel.to.ch ? 0 : 1); + for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode); + } + + function indentLine(n, how) { + if (!how) how = "add"; + if (how == "smart") { + if (!mode.indent) how = "prev"; + else var state = getStateBefore(n); + } + + var line = getLine(n), curSpace = line.indentation(options.tabSize), + curSpaceString = line.text.match(/^\s*/)[0], indentation; + if (how == "prev") { + if (n) indentation = getLine(n-1).indentation(options.tabSize); + else indentation = 0; + } + else if (how == "smart") indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text); + else if (how == "add") indentation = curSpace + options.indentUnit; + else if (how == "subtract") indentation = curSpace - options.indentUnit; + indentation = Math.max(0, indentation); + var diff = indentation - curSpace; + + if (!diff) { + if (sel.from.line != n && sel.to.line != n) return; + var indentString = curSpaceString; + } + else { + var indentString = "", pos = 0; + if (options.indentWithTabs) + for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";} + while (pos < indentation) {++pos; indentString += " ";} + } + + replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length}); + } + + function loadMode() { + mode = CodeMirror.getMode(options, options.mode); + doc.iter(0, doc.size, function(line) { line.stateAfter = null; }); + work = [0]; + startWorker(); + } + function gutterChanged() { + var visible = options.gutter || options.lineNumbers; + gutter.style.display = visible ? "" : "none"; + if (visible) gutterDirty = true; + else lineDiv.parentNode.style.marginLeft = 0; + } + function wrappingChanged(from, to) { + if (options.lineWrapping) { + wrapper.className += " CodeMirror-wrap"; + var perLine = scroller.clientWidth / charWidth() - 3; + doc.iter(0, doc.size, function(line) { + if (line.hidden) return; + var guess = Math.ceil(line.text.length / perLine) || 1; + if (guess != 1) updateLineHeight(line, guess); + }); + lineSpace.style.width = code.style.width = ""; + } else { + wrapper.className = wrapper.className.replace(" CodeMirror-wrap", ""); + maxWidth = null; maxLine = ""; + doc.iter(0, doc.size, function(line) { + if (line.height != 1 && !line.hidden) updateLineHeight(line, 1); + if (line.text.length > maxLine.length) maxLine = line.text; + }); + } + changes.push({from: 0, to: doc.size}); + } + function computeTabText() { + for (var str = '', i = 0; i < options.tabSize; ++i) str += " "; + return str + ""; + } + function tabsChanged() { + tabText = computeTabText(); + updateDisplay(true); + } + function themeChanged() { + scroller.className = scroller.className.replace(/\s*cm-s-\w+/g, "") + + options.theme.replace(/(^|\s)\s*/g, " cm-s-"); + } + + function TextMarker() { this.set = []; } + TextMarker.prototype.clear = operation(function() { + var min = Infinity, max = -Infinity; + for (var i = 0, e = this.set.length; i < e; ++i) { + var line = this.set[i], mk = line.marked; + if (!mk || !line.parent) continue; + var lineN = lineNo(line); + min = Math.min(min, lineN); max = Math.max(max, lineN); + for (var j = 0; j < mk.length; ++j) + if (mk[j].set == this.set) mk.splice(j--, 1); + } + if (min != Infinity) + changes.push({from: min, to: max + 1}); + }); + TextMarker.prototype.find = function() { + var from, to; + for (var i = 0, e = this.set.length; i < e; ++i) { + var line = this.set[i], mk = line.marked; + for (var j = 0; j < mk.length; ++j) { + var mark = mk[j]; + if (mark.set == this.set) { + if (mark.from != null || mark.to != null) { + var found = lineNo(line); + if (found != null) { + if (mark.from != null) from = {line: found, ch: mark.from}; + if (mark.to != null) to = {line: found, ch: mark.to}; + } + } + } + } + } + return {from: from, to: to}; + }; + + function markText(from, to, className) { + from = clipPos(from); to = clipPos(to); + var tm = new TextMarker(); + function add(line, from, to, className) { + getLine(line).addMark(new MarkedText(from, to, className, tm.set)); + } + if (from.line == to.line) add(from.line, from.ch, to.ch, className); + else { + add(from.line, from.ch, null, className); + for (var i = from.line + 1, e = to.line; i < e; ++i) + add(i, null, null, className); + add(to.line, null, to.ch, className); + } + changes.push({from: from.line, to: to.line + 1}); + return tm; + } + + function setBookmark(pos) { + pos = clipPos(pos); + var bm = new Bookmark(pos.ch); + getLine(pos.line).addMark(bm); + return bm; + } + + function addGutterMarker(line, text, className) { + if (typeof line == "number") line = getLine(clipLine(line)); + line.gutterMarker = {text: text, style: className}; + gutterDirty = true; + return line; + } + function removeGutterMarker(line) { + if (typeof line == "number") line = getLine(clipLine(line)); + line.gutterMarker = null; + gutterDirty = true; + } + + function changeLine(handle, op) { + var no = handle, line = handle; + if (typeof handle == "number") line = getLine(clipLine(handle)); + else no = lineNo(handle); + if (no == null) return null; + if (op(line, no)) changes.push({from: no, to: no + 1}); + else return null; + return line; + } + function setLineClass(handle, className) { + return changeLine(handle, function(line) { + if (line.className != className) { + line.className = className; + return true; + } + }); + } + function setLineHidden(handle, hidden) { + return changeLine(handle, function(line, no) { + if (line.hidden != hidden) { + line.hidden = hidden; + updateLineHeight(line, hidden ? 0 : 1); + var fline = sel.from.line, tline = sel.to.line; + if (hidden && (fline == no || tline == no)) { + var from = fline == no ? skipHidden({line: fline, ch: 0}, fline, 0) : sel.from; + var to = tline == no ? skipHidden({line: tline, ch: 0}, tline, 0) : sel.to; + setSelection(from, to); + } + return (gutterDirty = true); + } + }); + } + + function lineInfo(line) { + if (typeof line == "number") { + if (!isLine(line)) return null; + var n = line; + line = getLine(line); + if (!line) return null; + } + else { + var n = lineNo(line); + if (n == null) return null; + } + var marker = line.gutterMarker; + return {line: n, handle: line, text: line.text, markerText: marker && marker.text, + markerClass: marker && marker.style, lineClass: line.className}; + } + + function stringWidth(str) { + measure.innerHTML = "
x
"; + measure.firstChild.firstChild.firstChild.nodeValue = str; + return measure.firstChild.firstChild.offsetWidth || 10; + } + // These are used to go from pixel positions to character + // positions, taking varying character widths into account. + function charFromX(line, x) { + if (x <= 0) return 0; + var lineObj = getLine(line), text = lineObj.text; + function getX(len) { + measure.innerHTML = "
" + lineObj.getHTML(tabText, len) + "
"; + return measure.firstChild.firstChild.offsetWidth; + } + var from = 0, fromX = 0, to = text.length, toX; + // Guess a suitable upper bound for our search. + var estimated = Math.min(to, Math.ceil(x / charWidth())); + for (;;) { + var estX = getX(estimated); + if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2)); + else {toX = estX; to = estimated; break;} + } + if (x > toX) return to; + // Try to guess a suitable lower bound as well. + estimated = Math.floor(to * 0.8); estX = getX(estimated); + if (estX < x) {from = estimated; fromX = estX;} + // Do a binary search between these bounds. + for (;;) { + if (to - from <= 1) return (toX - x > x - fromX) ? from : to; + var middle = Math.ceil((from + to) / 2), middleX = getX(middle); + if (middleX > x) {to = middle; toX = middleX;} + else {from = middle; fromX = middleX;} + } + } + + var tempId = Math.floor(Math.random() * 0xffffff).toString(16); + function measureLine(line, ch) { + if (ch == 0) return {top: 0, left: 0}; + var extra = ""; + // Include extra text at the end to make sure the measured line is wrapped in the right way. + if (options.lineWrapping) { + var end = line.text.indexOf(" ", ch + 2); + extra = htmlEscape(line.text.slice(ch + 1, end < 0 ? line.text.length : end + (ie ? 5 : 0))); + } + measure.innerHTML = "
" + line.getHTML(tabText, ch) +
+        '' + htmlEscape(line.text.charAt(ch) || " ") + "" +
+        extra + "
"; + var elt = document.getElementById("CodeMirror-temp-" + tempId); + var top = elt.offsetTop, left = elt.offsetLeft; + // Older IEs report zero offsets for spans directly after a wrap + if (ie && top == 0 && left == 0) { + var backup = document.createElement("span"); + backup.innerHTML = "x"; + elt.parentNode.insertBefore(backup, elt.nextSibling); + top = backup.offsetTop; + } + return {top: top, left: left}; + } + function localCoords(pos, inLineWrap) { + var x, lh = textHeight(), y = lh * (heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0)); + if (pos.ch == 0) x = 0; + else { + var sp = measureLine(getLine(pos.line), pos.ch); + x = sp.left; + if (options.lineWrapping) y += Math.max(0, sp.top); + } + return {x: x, y: y, yBot: y + lh}; + } + // Coords must be lineSpace-local + function coordsChar(x, y) { + if (y < 0) y = 0; + var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th); + var lineNo = lineAtHeight(doc, heightPos); + if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc.size - 1).text.length}; + var lineObj = getLine(lineNo), text = lineObj.text; + var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0; + if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0}; + function getX(len) { + var sp = measureLine(lineObj, len); + if (tw) { + var off = Math.round(sp.top / th); + return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth); + } + return sp.left; + } + var from = 0, fromX = 0, to = text.length, toX; + // Guess a suitable upper bound for our search. + var estimated = Math.min(to, Math.ceil((x + innerOff * scroller.clientWidth * .9) / cw)); + for (;;) { + var estX = getX(estimated); + if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2)); + else {toX = estX; to = estimated; break;} + } + if (x > toX) return {line: lineNo, ch: to}; + // Try to guess a suitable lower bound as well. + estimated = Math.floor(to * 0.8); estX = getX(estimated); + if (estX < x) {from = estimated; fromX = estX;} + // Do a binary search between these bounds. + for (;;) { + if (to - from <= 1) return {line: lineNo, ch: (toX - x > x - fromX) ? from : to}; + var middle = Math.ceil((from + to) / 2), middleX = getX(middle); + if (middleX > x) {to = middle; toX = middleX;} + else {from = middle; fromX = middleX;} + } + } + function pageCoords(pos) { + var local = localCoords(pos, true), off = eltOffset(lineSpace); + return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot}; + } + + var cachedHeight, cachedHeightFor, measureText; + function textHeight() { + if (measureText == null) { + measureText = "
";
+        for (var i = 0; i < 49; ++i) measureText += "x
"; + measureText += "x
"; + } + var offsetHeight = lineDiv.clientHeight; + if (offsetHeight == cachedHeightFor) return cachedHeight; + cachedHeightFor = offsetHeight; + measure.innerHTML = measureText; + cachedHeight = measure.firstChild.offsetHeight / 50 || 1; + measure.innerHTML = ""; + return cachedHeight; + } + var cachedWidth, cachedWidthFor = 0; + function charWidth() { + if (scroller.clientWidth == cachedWidthFor) return cachedWidth; + cachedWidthFor = scroller.clientWidth; + return (cachedWidth = stringWidth("x")); + } + function paddingTop() {return lineSpace.offsetTop;} + function paddingLeft() {return lineSpace.offsetLeft;} + + function posFromMouse(e, liberal) { + var offW = eltOffset(scroller, true), x, y; + // Fails unpredictably on IE[67] when mouse is dragged around quickly. + try { x = e.clientX; y = e.clientY; } catch (e) { return null; } + // This is a mess of a heuristic to try and determine whether a + // scroll-bar was clicked or not, and to return null if one was + // (and !liberal). + if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight)) + return null; + var offL = eltOffset(lineSpace, true); + return coordsChar(x - offL.left, y - offL.top); + } + function onContextMenu(e) { + var pos = posFromMouse(e); + if (!pos || window.opera) return; // Opera is difficult. + if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)) + operation(setCursor)(pos.line, pos.ch); + + var oldCSS = input.style.cssText; + inputDiv.style.position = "absolute"; + input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + + "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " + + "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; + leaveInputAlone = true; + var val = input.value = getSelection(); + focusInput(); + input.select(); + function rehide() { + var newVal = splitLines(input.value).join("\n"); + if (newVal != val) operation(replaceSelection)(newVal, "end"); + inputDiv.style.position = "relative"; + input.style.cssText = oldCSS; + leaveInputAlone = false; + resetInput(true); + slowPoll(); + } + + if (gecko) { + e_stop(e); + var mouseup = connect(window, "mouseup", function() { + mouseup(); + setTimeout(rehide, 20); + }, true); + } + else { + setTimeout(rehide, 50); + } + } + + // Cursor-blinking + function restartBlink() { + clearInterval(blinker); + var on = true; + cursor.style.visibility = ""; + blinker = setInterval(function() { + cursor.style.visibility = (on = !on) ? "" : "hidden"; + }, 650); + } + + var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"}; + function matchBrackets(autoclear) { + var head = sel.inverted ? sel.from : sel.to, line = getLine(head.line), pos = head.ch - 1; + var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)]; + if (!match) return; + var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles; + for (var off = pos + 1, i = 0, e = st.length; i < e; i+=2) + if ((off -= st[i].length) <= 0) {var style = st[i+1]; break;} + + var stack = [line.text.charAt(pos)], re = /[(){}[\]]/; + function scan(line, from, to) { + if (!line.text) return; + var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur; + for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2*d) { + var text = st[i]; + if (st[i+1] != null && st[i+1] != style) {pos += d * text.length; continue;} + for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos+=d) { + if (pos >= from && pos < to && re.test(cur = text.charAt(j))) { + var match = matching[cur]; + if (match.charAt(1) == ">" == forward) stack.push(cur); + else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false}; + else if (!stack.length) return {pos: pos, match: true}; + } + } + } + } + for (var i = head.line, e = forward ? Math.min(i + 100, doc.size) : Math.max(-1, i - 100); i != e; i+=d) { + var line = getLine(i), first = i == head.line; + var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length); + if (found) break; + } + if (!found) found = {pos: null, match: false}; + var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket"; + var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style), + two = found.pos != null && markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style); + var clear = operation(function(){one.clear(); two && two.clear();}); + if (autoclear) setTimeout(clear, 800); + else bracketHighlighted = clear; + } + + // Finds the line to start with when starting a parse. Tries to + // find a line with a stateAfter, so that it can start with a + // valid state. If that fails, it returns the line with the + // smallest indentation, which tends to need the least context to + // parse correctly. + function findStartLine(n) { + var minindent, minline; + for (var search = n, lim = n - 40; search > lim; --search) { + if (search == 0) return 0; + var line = getLine(search-1); + if (line.stateAfter) return search; + var indented = line.indentation(options.tabSize); + if (minline == null || minindent > indented) { + minline = search - 1; + minindent = indented; + } + } + return minline; + } + function getStateBefore(n) { + var start = findStartLine(n), state = start && getLine(start-1).stateAfter; + if (!state) state = startState(mode); + else state = copyState(mode, state); + doc.iter(start, n, function(line) { + line.highlight(mode, state, options.tabSize); + line.stateAfter = copyState(mode, state); + }); + if (start < n) changes.push({from: start, to: n}); + if (n < doc.size && !getLine(n).stateAfter) work.push(n); + return state; + } + function highlightLines(start, end) { + var state = getStateBefore(start); + doc.iter(start, end, function(line) { + line.highlight(mode, state, options.tabSize); + line.stateAfter = copyState(mode, state); + }); + } + function highlightWorker() { + var end = +new Date + options.workTime; + var foundWork = work.length; + while (work.length) { + if (!getLine(showingFrom).stateAfter) var task = showingFrom; + else var task = work.pop(); + if (task >= doc.size) continue; + var start = findStartLine(task), state = start && getLine(start-1).stateAfter; + if (state) state = copyState(mode, state); + else state = startState(mode); + + var unchanged = 0, compare = mode.compareStates, realChange = false, + i = start, bail = false; + doc.iter(i, doc.size, function(line) { + var hadState = line.stateAfter; + if (+new Date > end) { + work.push(i); + startWorker(options.workDelay); + if (realChange) changes.push({from: task, to: i + 1}); + return (bail = true); + } + var changed = line.highlight(mode, state, options.tabSize); + if (changed) realChange = true; + line.stateAfter = copyState(mode, state); + if (compare) { + if (hadState && compare(hadState, state)) return true; + } else { + if (changed !== false || !hadState) unchanged = 0; + else if (++unchanged > 3 && (!mode.indent || mode.indent(hadState, "") == mode.indent(state, ""))) + return true; + } + ++i; + }); + if (bail) return; + if (realChange) changes.push({from: task, to: i + 1}); + } + if (foundWork && options.onHighlightComplete) + options.onHighlightComplete(instance); + } + function startWorker(time) { + if (!work.length) return; + highlight.set(time, operation(highlightWorker)); + } + + // Operations are used to wrap changes in such a way that each + // change won't have to update the cursor and display (which would + // be awkward, slow, and error-prone), but instead updates are + // batched and then all combined and executed at once. + function startOperation() { + updateInput = userSelChange = textChanged = null; + changes = []; selectionChanged = false; callbacks = []; + } + function endOperation() { + var reScroll = false, updated; + if (selectionChanged) reScroll = !scrollCursorIntoView(); + if (changes.length) updated = updateDisplay(changes, true); + else { + if (selectionChanged) updateSelection(); + if (gutterDirty) updateGutter(); + } + if (reScroll) scrollCursorIntoView(); + if (selectionChanged) {scrollEditorIntoView(); restartBlink();} + + if (focused && !leaveInputAlone && + (updateInput === true || (updateInput !== false && selectionChanged))) + resetInput(userSelChange); + + if (selectionChanged && options.matchBrackets) + setTimeout(operation(function() { + if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;} + if (posEq(sel.from, sel.to)) matchBrackets(false); + }), 20); + var tc = textChanged, cbs = callbacks; // these can be reset by callbacks + if (selectionChanged && options.onCursorActivity) + options.onCursorActivity(instance); + if (tc && options.onChange && instance) + options.onChange(instance, tc); + for (var i = 0; i < cbs.length; ++i) cbs[i](instance); + if (updated && options.onUpdate) options.onUpdate(instance); + } + var nestedOperation = 0; + function operation(f) { + return function() { + if (!nestedOperation++) startOperation(); + try {var result = f.apply(this, arguments);} + finally {if (!--nestedOperation) endOperation();} + return result; + }; + } + + for (var ext in extensions) + if (extensions.propertyIsEnumerable(ext) && + !instance.propertyIsEnumerable(ext)) + instance[ext] = extensions[ext]; + return instance; + } // (end of function CodeMirror) + + // The default configuration options. + CodeMirror.defaults = { + value: "", + mode: null, + theme: "default", + indentUnit: 2, + indentWithTabs: false, + smartIndent: true, + tabSize: 4, + keyMap: "default", + extraKeys: null, + electricChars: true, + onKeyEvent: null, + lineWrapping: false, + lineNumbers: false, + gutter: false, + fixedGutter: false, + firstLineNumber: 1, + readOnly: false, + onChange: null, + onCursorActivity: null, + onGutterClick: null, + onHighlightComplete: null, + onUpdate: null, + onFocus: null, onBlur: null, onScroll: null, + matchBrackets: false, + workTime: 100, + workDelay: 200, + pollInterval: 100, + undoDepth: 40, + tabindex: null, + document: window.document + }; + + var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); + var mac = ios || /Mac/.test(navigator.platform); + var win = /Win/.test(navigator.platform); + + // Known modes, by name and by MIME + var modes = {}, mimeModes = {}; + CodeMirror.defineMode = function(name, mode) { + if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; + modes[name] = mode; + }; + CodeMirror.defineMIME = function(mime, spec) { + mimeModes[mime] = spec; + }; + CodeMirror.getMode = function(options, spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) + spec = mimeModes[spec]; + if (typeof spec == "string") + var mname = spec, config = {}; + else if (spec != null) + var mname = spec.name, config = spec; + var mfactory = modes[mname]; + if (!mfactory) { + if (window.console) console.warn("No mode " + mname + " found, falling back to plain text."); + return CodeMirror.getMode(options, "text/plain"); + } + return mfactory(options, config || {}); + }; + CodeMirror.listModes = function() { + var list = []; + for (var m in modes) + if (modes.propertyIsEnumerable(m)) list.push(m); + return list; + }; + CodeMirror.listMIMEs = function() { + var list = []; + for (var m in mimeModes) + if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]}); + return list; + }; + + var extensions = CodeMirror.extensions = {}; + CodeMirror.defineExtension = function(name, func) { + extensions[name] = func; + }; + + var commands = CodeMirror.commands = { + selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});}, + killLine: function(cm) { + var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to); + if (!sel && cm.getLine(from.line).length == from.ch) cm.replaceRange("", from, {line: from.line + 1, ch: 0}); + else cm.replaceRange("", from, sel ? to : {line: from.line}); + }, + deleteLine: function(cm) {var l = cm.getCursor().line; cm.replaceRange("", {line: l, ch: 0}, {line: l});}, + undo: function(cm) {cm.undo();}, + redo: function(cm) {cm.redo();}, + goDocStart: function(cm) {cm.setCursor(0, 0, true);}, + goDocEnd: function(cm) {cm.setSelection({line: cm.lineCount() - 1}, null, true);}, + goLineStart: function(cm) {cm.setCursor(cm.getCursor().line, 0, true);}, + goLineStartSmart: function(cm) { + var cur = cm.getCursor(); + var text = cm.getLine(cur.line), firstNonWS = Math.max(0, text.search(/\S/)); + cm.setCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true); + }, + goLineEnd: function(cm) {cm.setSelection({line: cm.getCursor().line}, null, true);}, + goLineUp: function(cm) {cm.moveV(-1, "line");}, + goLineDown: function(cm) {cm.moveV(1, "line");}, + goPageUp: function(cm) {cm.moveV(-1, "page");}, + goPageDown: function(cm) {cm.moveV(1, "page");}, + goCharLeft: function(cm) {cm.moveH(-1, "char");}, + goCharRight: function(cm) {cm.moveH(1, "char");}, + goColumnLeft: function(cm) {cm.moveH(-1, "column");}, + goColumnRight: function(cm) {cm.moveH(1, "column");}, + goWordLeft: function(cm) {cm.moveH(-1, "word");}, + goWordRight: function(cm) {cm.moveH(1, "word");}, + delCharLeft: function(cm) {cm.deleteH(-1, "char");}, + delCharRight: function(cm) {cm.deleteH(1, "char");}, + delWordLeft: function(cm) {cm.deleteH(-1, "word");}, + delWordRight: function(cm) {cm.deleteH(1, "word");}, + indentAuto: function(cm) {cm.indentSelection("smart");}, + indentMore: function(cm) {cm.indentSelection("add");}, + indentLess: function(cm) {cm.indentSelection("subtract");}, + insertTab: function(cm) {cm.replaceSelection("\t", "end");}, + transposeChars: function(cm) { + var cur = cm.getCursor(), line = cm.getLine(cur.line); + if (cur.ch > 0 && cur.ch < line.length - 1) + cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1), + {line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1}); + }, + newlineAndIndent: function(cm) { + cm.replaceSelection("\n", "end"); + cm.indentLine(cm.getCursor().line); + }, + toggleOverwrite: function(cm) {cm.toggleOverwrite();} + }; + + var keyMap = CodeMirror.keyMap = {}; + keyMap.basic = { + "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", + "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", + "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "indentMore", "Shift-Tab": "indentLess", + "Enter": "newlineAndIndent", "Insert": "toggleOverwrite" + }; + // Note that the save and find-related commands aren't defined by + // default. Unknown commands are simply ignored. + keyMap.pcDefault = { + "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", + "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd", + "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", + "Ctrl-Backspace": "delWordLeft", "Ctrl-Delete": "delWordRight", "Ctrl-S": "save", "Ctrl-F": "find", + "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", + fallthrough: "basic" + }; + keyMap.macDefault = { + "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", + "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goWordLeft", + "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordLeft", + "Ctrl-Alt-Backspace": "delWordRight", "Alt-Delete": "delWordRight", "Cmd-S": "save", "Cmd-F": "find", + "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", + fallthrough: ["basic", "emacsy"] + }; + keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; + keyMap.emacsy = { + "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", + "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", + "Ctrl-V": "goPageUp", "Shift-Ctrl-V": "goPageDown", "Ctrl-D": "delCharRight", "Ctrl-H": "delCharLeft", + "Alt-D": "delWordRight", "Alt-Backspace": "delWordLeft", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars" + }; + + function lookupKey(name, extraMap, map) { + function lookup(name, map, ft) { + var found = map[name]; + if (found != null) return found; + if (ft == null) ft = map.fallthrough; + if (ft == null) return map.catchall; + if (typeof ft == "string") return lookup(name, keyMap[ft]); + for (var i = 0, e = ft.length; i < e; ++i) { + found = lookup(name, keyMap[ft[i]]); + if (found != null) return found; + } + return null; + } + return extraMap ? lookup(name, extraMap, map) : lookup(name, keyMap[map]); + } + function isModifierKey(event) { + var name = keyNames[e_prop(event, "keyCode")]; + return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; + } + + CodeMirror.fromTextArea = function(textarea, options) { + if (!options) options = {}; + options.value = textarea.value; + if (!options.tabindex && textarea.tabindex) + options.tabindex = textarea.tabindex; + + function save() {textarea.value = instance.getValue();} + if (textarea.form) { + // Deplorable hack to make the submit method do the right thing. + var rmSubmit = connect(textarea.form, "submit", save, true); + if (typeof textarea.form.submit == "function") { + var realSubmit = textarea.form.submit; + function wrappedSubmit() { + save(); + textarea.form.submit = realSubmit; + textarea.form.submit(); + textarea.form.submit = wrappedSubmit; + } + textarea.form.submit = wrappedSubmit; + } + } + + textarea.style.display = "none"; + var instance = CodeMirror(function(node) { + textarea.parentNode.insertBefore(node, textarea.nextSibling); + }, options); + instance.save = save; + instance.getTextArea = function() { return textarea; }; + instance.toTextArea = function() { + save(); + textarea.parentNode.removeChild(instance.getWrapperElement()); + textarea.style.display = ""; + if (textarea.form) { + rmSubmit(); + if (typeof textarea.form.submit == "function") + textarea.form.submit = realSubmit; + } + }; + return instance; + }; + + // Utility functions for working with state. Exported because modes + // sometimes need to do this. + function copyState(mode, state) { + if (state === true) return state; + if (mode.copyState) return mode.copyState(state); + var nstate = {}; + for (var n in state) { + var val = state[n]; + if (val instanceof Array) val = val.concat([]); + nstate[n] = val; + } + return nstate; + } + CodeMirror.copyState = copyState; + function startState(mode, a1, a2) { + return mode.startState ? mode.startState(a1, a2) : true; + } + CodeMirror.startState = startState; + + // The character stream used by a mode's parser. + function StringStream(string, tabSize) { + this.pos = this.start = 0; + this.string = string; + this.tabSize = tabSize || 8; + } + StringStream.prototype = { + eol: function() {return this.pos >= this.string.length;}, + sol: function() {return this.pos == 0;}, + peek: function() {return this.string.charAt(this.pos);}, + next: function() { + if (this.pos < this.string.length) + return this.string.charAt(this.pos++); + }, + eat: function(match) { + var ch = this.string.charAt(this.pos); + if (typeof match == "string") var ok = ch == match; + else var ok = ch && (match.test ? match.test(ch) : match(ch)); + if (ok) {++this.pos; return ch;} + }, + eatWhile: function(match) { + var start = this.pos; + while (this.eat(match)){} + return this.pos > start; + }, + eatSpace: function() { + var start = this.pos; + while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; + return this.pos > start; + }, + skipToEnd: function() {this.pos = this.string.length;}, + skipTo: function(ch) { + var found = this.string.indexOf(ch, this.pos); + if (found > -1) {this.pos = found; return true;} + }, + backUp: function(n) {this.pos -= n;}, + column: function() {return countColumn(this.string, this.start, this.tabSize);}, + indentation: function() {return countColumn(this.string, null, this.tabSize);}, + match: function(pattern, consume, caseInsensitive) { + if (typeof pattern == "string") { + function cased(str) {return caseInsensitive ? str.toLowerCase() : str;} + if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { + if (consume !== false) this.pos += pattern.length; + return true; + } + } + else { + var match = this.string.slice(this.pos).match(pattern); + if (match && consume !== false) this.pos += match[0].length; + return match; + } + }, + current: function(){return this.string.slice(this.start, this.pos);} + }; + CodeMirror.StringStream = StringStream; + + function MarkedText(from, to, className, set) { + this.from = from; this.to = to; this.style = className; this.set = set; + } + MarkedText.prototype = { + attach: function(line) { this.set.push(line); }, + detach: function(line) { + var ix = indexOf(this.set, line); + if (ix > -1) this.set.splice(ix, 1); + }, + split: function(pos, lenBefore) { + if (this.to <= pos && this.to != null) return null; + var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore; + var to = this.to == null ? null : this.to - pos + lenBefore; + return new MarkedText(from, to, this.style, this.set); + }, + dup: function() { return new MarkedText(null, null, this.style, this.set); }, + clipTo: function(fromOpen, from, toOpen, to, diff) { + if (this.from != null && this.from >= from) + this.from = Math.max(to, this.from) + diff; + if (this.to != null && this.to > from) + this.to = to < this.to ? this.to + diff : from; + if (fromOpen && to > this.from && (to < this.to || this.to == null)) + this.from = null; + if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null)) + this.to = null; + }, + isDead: function() { return this.from != null && this.to != null && this.from >= this.to; }, + sameSet: function(x) { return this.set == x.set; } + }; + + function Bookmark(pos) { + this.from = pos; this.to = pos; this.line = null; + } + Bookmark.prototype = { + attach: function(line) { this.line = line; }, + detach: function(line) { if (this.line == line) this.line = null; }, + split: function(pos, lenBefore) { + if (pos < this.from) { + this.from = this.to = (this.from - pos) + lenBefore; + return this; + } + }, + isDead: function() { return this.from > this.to; }, + clipTo: function(fromOpen, from, toOpen, to, diff) { + if ((fromOpen || from < this.from) && (toOpen || to > this.to)) { + this.from = 0; this.to = -1; + } else if (this.from > from) { + this.from = this.to = Math.max(to, this.from) + diff; + } + }, + sameSet: function(x) { return false; }, + find: function() { + if (!this.line || !this.line.parent) return null; + return {line: lineNo(this.line), ch: this.from}; + }, + clear: function() { + if (this.line) { + var found = indexOf(this.line.marked, this); + if (found != -1) this.line.marked.splice(found, 1); + this.line = null; + } + } + }; + + // Line objects. These hold state related to a line, including + // highlighting info (the styles array). + function Line(text, styles) { + this.styles = styles || [text, null]; + this.text = text; + this.height = 1; + this.marked = this.gutterMarker = this.className = this.handlers = null; + this.stateAfter = this.parent = this.hidden = null; + } + Line.inheritMarks = function(text, orig) { + var ln = new Line(text), mk = orig && orig.marked; + if (mk) { + for (var i = 0; i < mk.length; ++i) { + if (mk[i].to == null && mk[i].style) { + var newmk = ln.marked || (ln.marked = []), mark = mk[i]; + var nmark = mark.dup(); newmk.push(nmark); nmark.attach(ln); + } + } + } + return ln; + } + Line.prototype = { + // Replace a piece of a line, keeping the styles around it intact. + replace: function(from, to_, text) { + var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_; + copyStyles(0, from, this.styles, st); + if (text) st.push(text, null); + copyStyles(to, this.text.length, this.styles, st); + this.styles = st; + this.text = this.text.slice(0, from) + text + this.text.slice(to); + this.stateAfter = null; + if (mk) { + var diff = text.length - (to - from); + for (var i = 0; i < mk.length; ++i) { + var mark = mk[i]; + mark.clipTo(from == null, from || 0, to_ == null, to, diff); + if (mark.isDead()) {mark.detach(this); mk.splice(i--, 1);} + } + } + }, + // Split a part off a line, keeping styles and markers intact. + split: function(pos, textBefore) { + var st = [textBefore, null], mk = this.marked; + copyStyles(pos, this.text.length, this.styles, st); + var taken = new Line(textBefore + this.text.slice(pos), st); + if (mk) { + for (var i = 0; i < mk.length; ++i) { + var mark = mk[i]; + var newmark = mark.split(pos, textBefore.length); + if (newmark) { + if (!taken.marked) taken.marked = []; + taken.marked.push(newmark); newmark.attach(taken); + } + } + } + return taken; + }, + append: function(line) { + var mylen = this.text.length, mk = line.marked, mymk = this.marked; + this.text += line.text; + copyStyles(0, line.text.length, line.styles, this.styles); + if (mymk) { + for (var i = 0; i < mymk.length; ++i) + if (mymk[i].to == null) mymk[i].to = mylen; + } + if (mk && mk.length) { + if (!mymk) this.marked = mymk = []; + outer: for (var i = 0; i < mk.length; ++i) { + var mark = mk[i]; + if (!mark.from) { + for (var j = 0; j < mymk.length; ++j) { + var mymark = mymk[j]; + if (mymark.to == mylen && mymark.sameSet(mark)) { + mymark.to = mark.to == null ? null : mark.to + mylen; + if (mymark.isDead()) { + mymark.detach(this); + mk.splice(i--, 1); + } + continue outer; + } + } + } + mymk.push(mark); + mark.attach(this); + mark.from += mylen; + if (mark.to != null) mark.to += mylen; + } + } + }, + fixMarkEnds: function(other) { + var mk = this.marked, omk = other.marked; + if (!mk) return; + for (var i = 0; i < mk.length; ++i) { + var mark = mk[i], close = mark.to == null; + if (close && omk) { + for (var j = 0; j < omk.length; ++j) + if (omk[j].sameSet(mark)) {close = false; break;} + } + if (close) mark.to = this.text.length; + } + }, + fixMarkStarts: function() { + var mk = this.marked; + if (!mk) return; + for (var i = 0; i < mk.length; ++i) + if (mk[i].from == null) mk[i].from = 0; + }, + addMark: function(mark) { + mark.attach(this); + if (this.marked == null) this.marked = []; + this.marked.push(mark); + this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);}); + }, + // Run the given mode's parser over a line, update the styles + // array, which contains alternating fragments of text and CSS + // classes. + highlight: function(mode, state, tabSize) { + var stream = new StringStream(this.text, tabSize), st = this.styles, pos = 0; + var changed = false, curWord = st[0], prevWord; + if (this.text == "" && mode.blankLine) mode.blankLine(state); + while (!stream.eol()) { + var style = mode.token(stream, state); + var substr = this.text.slice(stream.start, stream.pos); + stream.start = stream.pos; + if (pos && st[pos-1] == style) + st[pos-2] += substr; + else if (substr) { + if (!changed && (st[pos+1] != style || (pos && st[pos-2] != prevWord))) changed = true; + st[pos++] = substr; st[pos++] = style; + prevWord = curWord; curWord = st[pos]; + } + // Give up when line is ridiculously long + if (stream.pos > 5000) { + st[pos++] = this.text.slice(stream.pos); st[pos++] = null; + break; + } + } + if (st.length != pos) {st.length = pos; changed = true;} + if (pos && st[pos-2] != prevWord) changed = true; + // Short lines with simple highlights return null, and are + // counted as changed by the driver because they are likely to + // highlight the same way in various contexts. + return changed || (st.length < 5 && this.text.length < 10 ? null : false); + }, + // Fetch the parser token for a given character. Useful for hacks + // that want to inspect the mode state (say, for completion). + getTokenAt: function(mode, state, ch) { + var txt = this.text, stream = new StringStream(txt); + while (stream.pos < ch && !stream.eol()) { + stream.start = stream.pos; + var style = mode.token(stream, state); + } + return {start: stream.start, + end: stream.pos, + string: stream.current(), + className: style || null, + state: state}; + }, + indentation: function(tabSize) {return countColumn(this.text, null, tabSize);}, + // Produces an HTML fragment for the line, taking selection, + // marking, and highlighting into account. + getHTML: function(tabText, endAt) { + var html = [], first = true; + function span(text, style) { + if (!text) return; + // Work around a bug where, in some compat modes, IE ignores leading spaces + if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1); + first = false; + if (style) html.push('', htmlEscape(text).replace(/\t/g, tabText), ""); + else html.push(htmlEscape(text).replace(/\t/g, tabText)); + } + var st = this.styles, allText = this.text, marked = this.marked; + var len = allText.length; + if (endAt != null) len = Math.min(endAt, len); + function styleToClass(style) { + if (!style) return null; + return "cm-" + style.replace(/ +/g, " cm-"); + } + + if (!allText && endAt == null) + span(" "); + else if (!marked || !marked.length) + for (var i = 0, ch = 0; ch < len; i+=2) { + var str = st[i], style = st[i+1], l = str.length; + if (ch + l > len) str = str.slice(0, len - ch); + ch += l; + span(str, styleToClass(style)); + } + else { + var pos = 0, i = 0, text = "", style, sg = 0; + var nextChange = marked[0].from || 0, marks = [], markpos = 0; + function advanceMarks() { + var m; + while (markpos < marked.length && + ((m = marked[markpos]).from == pos || m.from == null)) { + if (m.style != null) marks.push(m); + ++markpos; + } + nextChange = markpos < marked.length ? marked[markpos].from : Infinity; + for (var i = 0; i < marks.length; ++i) { + var to = marks[i].to || Infinity; + if (to == pos) marks.splice(i--, 1); + else nextChange = Math.min(to, nextChange); + } + } + var m = 0; + while (pos < len) { + if (nextChange == pos) advanceMarks(); + var upto = Math.min(len, nextChange); + while (true) { + if (text) { + var end = pos + text.length; + var appliedStyle = style; + for (var j = 0; j < marks.length; ++j) + appliedStyle = (appliedStyle ? appliedStyle + " " : "") + marks[j].style; + span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle); + if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} + pos = end; + } + text = st[i++]; style = styleToClass(st[i++]); + } + } + } + return html.join(""); + }, + cleanUp: function() { + this.parent = null; + if (this.marked) + for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this); + } + }; + // Utility used by replace and split above + function copyStyles(from, to, source, dest) { + for (var i = 0, pos = 0, state = 0; pos < to; i+=2) { + var part = source[i], end = pos + part.length; + if (state == 0) { + if (end > from) dest.push(part.slice(from - pos, Math.min(part.length, to - pos)), source[i+1]); + if (end >= from) state = 1; + } + else if (state == 1) { + if (end > to) dest.push(part.slice(0, to - pos), source[i+1]); + else dest.push(part, source[i+1]); + } + pos = end; + } + } + + // Data structure that holds the sequence of lines. + function LeafChunk(lines) { + this.lines = lines; + this.parent = null; + for (var i = 0, e = lines.length, height = 0; i < e; ++i) { + lines[i].parent = this; + height += lines[i].height; + } + this.height = height; + } + LeafChunk.prototype = { + chunkSize: function() { return this.lines.length; }, + remove: function(at, n, callbacks) { + for (var i = at, e = at + n; i < e; ++i) { + var line = this.lines[i]; + this.height -= line.height; + line.cleanUp(); + if (line.handlers) + for (var j = 0; j < line.handlers.length; ++j) callbacks.push(line.handlers[j]); + } + this.lines.splice(at, n); + }, + collapse: function(lines) { + lines.splice.apply(lines, [lines.length, 0].concat(this.lines)); + }, + insertHeight: function(at, lines, height) { + this.height += height; + this.lines.splice.apply(this.lines, [at, 0].concat(lines)); + for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this; + }, + iterN: function(at, n, op) { + for (var e = at + n; at < e; ++at) + if (op(this.lines[at])) return true; + } + }; + function BranchChunk(children) { + this.children = children; + var size = 0, height = 0; + for (var i = 0, e = children.length; i < e; ++i) { + var ch = children[i]; + size += ch.chunkSize(); height += ch.height; + ch.parent = this; + } + this.size = size; + this.height = height; + this.parent = null; + } + BranchChunk.prototype = { + chunkSize: function() { return this.size; }, + remove: function(at, n, callbacks) { + this.size -= n; + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var rm = Math.min(n, sz - at), oldHeight = child.height; + child.remove(at, rm, callbacks); + this.height -= oldHeight - child.height; + if (sz == rm) { this.children.splice(i--, 1); child.parent = null; } + if ((n -= rm) == 0) break; + at = 0; + } else at -= sz; + } + if (this.size - n < 25) { + var lines = []; + this.collapse(lines); + this.children = [new LeafChunk(lines)]; + this.children[0].parent = this; + } + }, + collapse: function(lines) { + for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines); + }, + insert: function(at, lines) { + var height = 0; + for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height; + this.insertHeight(at, lines, height); + }, + insertHeight: function(at, lines, height) { + this.size += lines.length; + this.height += height; + for (var i = 0, e = this.children.length; i < e; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at <= sz) { + child.insertHeight(at, lines, height); + if (child.lines && child.lines.length > 50) { + while (child.lines.length > 50) { + var spilled = child.lines.splice(child.lines.length - 25, 25); + var newleaf = new LeafChunk(spilled); + child.height -= newleaf.height; + this.children.splice(i + 1, 0, newleaf); + newleaf.parent = this; + } + this.maybeSpill(); + } + break; + } + at -= sz; + } + }, + maybeSpill: function() { + if (this.children.length <= 10) return; + var me = this; + do { + var spilled = me.children.splice(me.children.length - 5, 5); + var sibling = new BranchChunk(spilled); + if (!me.parent) { // Become the parent node + var copy = new BranchChunk(me.children); + copy.parent = me; + me.children = [copy, sibling]; + me = copy; + } else { + me.size -= sibling.size; + me.height -= sibling.height; + var myIndex = indexOf(me.parent.children, me); + me.parent.children.splice(myIndex + 1, 0, sibling); + } + sibling.parent = me.parent; + } while (me.children.length > 10); + me.parent.maybeSpill(); + }, + iter: function(from, to, op) { this.iterN(from, to - from, op); }, + iterN: function(at, n, op) { + for (var i = 0, e = this.children.length; i < e; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var used = Math.min(n, sz - at); + if (child.iterN(at, used, op)) return true; + if ((n -= used) == 0) break; + at = 0; + } else at -= sz; + } + } + }; + + function getLineAt(chunk, n) { + while (!chunk.lines) { + for (var i = 0;; ++i) { + var child = chunk.children[i], sz = child.chunkSize(); + if (n < sz) { chunk = child; break; } + n -= sz; + } + } + return chunk.lines[n]; + } + function lineNo(line) { + if (line.parent == null) return null; + var cur = line.parent, no = indexOf(cur.lines, line); + for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { + for (var i = 0, e = chunk.children.length; ; ++i) { + if (chunk.children[i] == cur) break; + no += chunk.children[i].chunkSize(); + } + } + return no; + } + function lineAtHeight(chunk, h) { + var n = 0; + outer: do { + for (var i = 0, e = chunk.children.length; i < e; ++i) { + var child = chunk.children[i], ch = child.height; + if (h < ch) { chunk = child; continue outer; } + h -= ch; + n += child.chunkSize(); + } + return n; + } while (!chunk.lines); + for (var i = 0, e = chunk.lines.length; i < e; ++i) { + var line = chunk.lines[i], lh = line.height; + if (h < lh) break; + h -= lh; + } + return n + i; + } + function heightAtLine(chunk, n) { + var h = 0; + outer: do { + for (var i = 0, e = chunk.children.length; i < e; ++i) { + var child = chunk.children[i], sz = child.chunkSize(); + if (n < sz) { chunk = child; continue outer; } + n -= sz; + h += child.height; + } + return h; + } while (!chunk.lines); + for (var i = 0; i < n; ++i) h += chunk.lines[i].height; + return h; + } + + // The history object 'chunks' changes that are made close together + // and at almost the same time into bigger undoable units. + function History() { + this.time = 0; + this.done = []; this.undone = []; + } + History.prototype = { + addChange: function(start, added, old) { + this.undone.length = 0; + var time = +new Date, cur = this.done[this.done.length - 1], last = cur && cur[cur.length - 1]; + var dtime = time - this.time; + if (dtime > 400 || !last) { + this.done.push([{start: start, added: added, old: old}]); + } else if (last.start > start + added || last.start + last.added < start - last.added + last.old.length) { + cur.push({start: start, added: added, old: old}); + } else { + var oldoff = 0; + if (start < last.start) { + for (var i = last.start - start - 1; i >= 0; --i) + last.old.unshift(old[i]); + last.added += last.start - start; + last.start = start; + } + else if (last.start < start) { + oldoff = start - last.start; + added += oldoff; + } + for (var i = last.added - oldoff, e = old.length; i < e; ++i) + last.old.push(old[i]); + if (last.added < added) last.added = added; + } + this.time = time; + } + }; + + function stopMethod() {e_stop(this);} + // Ensure an event has a stop method. + function addStop(event) { + if (!event.stop) event.stop = stopMethod; + return event; + } + + function e_preventDefault(e) { + if (e.preventDefault) e.preventDefault(); + else e.returnValue = false; + } + function e_stopPropagation(e) { + if (e.stopPropagation) e.stopPropagation(); + else e.cancelBubble = true; + } + function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);} + CodeMirror.e_stop = e_stop; + CodeMirror.e_preventDefault = e_preventDefault; + CodeMirror.e_stopPropagation = e_stopPropagation; + + function e_target(e) {return e.target || e.srcElement;} + function e_button(e) { + if (e.which) return e.which; + else if (e.button & 1) return 1; + else if (e.button & 2) return 3; + else if (e.button & 4) return 2; + } + + // Allow 3rd-party code to override event properties by adding an override + // object to an event object. + function e_prop(e, prop) { + var overridden = e.override && e.override.hasOwnProperty(prop); + return overridden ? e.override[prop] : e[prop]; + } + + // Event handler registration. If disconnect is true, it'll return a + // function that unregisters the handler. + function connect(node, type, handler, disconnect) { + if (typeof node.addEventListener == "function") { + node.addEventListener(type, handler, false); + if (disconnect) return function() {node.removeEventListener(type, handler, false);}; + } + else { + var wrapHandler = function(event) {handler(event || window.event);}; + node.attachEvent("on" + type, wrapHandler); + if (disconnect) return function() {node.detachEvent("on" + type, wrapHandler);}; + } + } + CodeMirror.connect = connect; + + function Delayed() {this.id = null;} + Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}}; + + // Detect drag-and-drop + var dragAndDrop = function() { + // IE8 has ondragstart and ondrop properties, but doesn't seem to + // actually support ondragstart the way it's supposed to work. + if (/MSIE [1-8]\b/.test(navigator.userAgent)) return false; + var div = document.createElement('div'); + return "draggable" in div; + }(); + + var gecko = /gecko\/\d{7}/i.test(navigator.userAgent); + var ie = /MSIE \d/.test(navigator.userAgent); + var webkit = /WebKit\//.test(navigator.userAgent); + + var lineSep = "\n"; + // Feature-detect whether newlines in textareas are converted to \r\n + (function () { + var te = document.createElement("textarea"); + te.value = "foo\nbar"; + if (te.value.indexOf("\r") > -1) lineSep = "\r\n"; + }()); + + // Counts the column offset in a string, taking tabs into account. + // Used mostly to find indentation. + function countColumn(string, end, tabSize) { + if (end == null) { + end = string.search(/[^\s\u00a0]/); + if (end == -1) end = string.length; + } + for (var i = 0, n = 0; i < end; ++i) { + if (string.charAt(i) == "\t") n += tabSize - (n % tabSize); + else ++n; + } + return n; + } + + function computedStyle(elt) { + if (elt.currentStyle) return elt.currentStyle; + return window.getComputedStyle(elt, null); + } + + // Find the position of an element by following the offsetParent chain. + // If screen==true, it returns screen (rather than page) coordinates. + function eltOffset(node, screen) { + var bod = node.ownerDocument.body; + var x = 0, y = 0, skipBody = false; + for (var n = node; n; n = n.offsetParent) { + var ol = n.offsetLeft, ot = n.offsetTop; + // Firefox reports weird inverted offsets when the body has a border. + if (n == bod) { x += Math.abs(ol); y += Math.abs(ot); } + else { x += ol, y += ot; } + if (screen && computedStyle(n).position == "fixed") + skipBody = true; + } + var e = screen && !skipBody ? null : bod; + for (var n = node.parentNode; n != e; n = n.parentNode) + if (n.scrollLeft != null) { x -= n.scrollLeft; y -= n.scrollTop;} + return {left: x, top: y}; + } + // Use the faster and saner getBoundingClientRect method when possible. + if (document.documentElement.getBoundingClientRect != null) eltOffset = function(node, screen) { + // Take the parts of bounding client rect that we are interested in so we are able to edit if need be, + // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page) + try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; } + catch(e) { box = {top: 0, left: 0}; } + if (!screen) { + // Get the toplevel scroll, working around browser differences. + if (window.pageYOffset == null) { + var t = document.documentElement || document.body.parentNode; + if (t.scrollTop == null) t = document.body; + box.top += t.scrollTop; box.left += t.scrollLeft; + } else { + box.top += window.pageYOffset; box.left += window.pageXOffset; + } + } + return box; + }; + + // Get a node's text content. + function eltText(node) { + return node.textContent || node.innerText || node.nodeValue || ""; + } + function selectInput(node) { + if (ios) { // Mobile Safari apparently has a bug where select() is broken. + node.selectionStart = 0; + node.selectionEnd = node.value.length; + } else node.select(); + } + + // Operations on {line, ch} objects. + function posEq(a, b) {return a.line == b.line && a.ch == b.ch;} + function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);} + function copyPos(x) {return {line: x.line, ch: x.ch};} + + var escapeElement = document.createElement("pre"); + function htmlEscape(str) { + escapeElement.textContent = str; + return escapeElement.innerHTML; + } + // Recent (late 2011) Opera betas insert bogus newlines at the start + // of the textContent, so we strip those. + if (htmlEscape("a") == "\na") + htmlEscape = function(str) { + escapeElement.textContent = str; + return escapeElement.innerHTML.slice(1); + }; + // Some IEs don't preserve tabs through innerHTML + else if (htmlEscape("\t") != "\t") + htmlEscape = function(str) { + escapeElement.innerHTML = ""; + escapeElement.appendChild(document.createTextNode(str)); + return escapeElement.innerHTML; + }; + CodeMirror.htmlEscape = htmlEscape; + + // Used to position the cursor after an undo/redo by finding the + // last edited character. + function editEnd(from, to) { + if (!to) return 0; + if (!from) return to.length; + for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j) + if (from.charAt(i) != to.charAt(j)) break; + return j + 1; + } + + function indexOf(collection, elt) { + if (collection.indexOf) return collection.indexOf(elt); + for (var i = 0, e = collection.length; i < e; ++i) + if (collection[i] == elt) return i; + return -1; + } + function isWordChar(ch) { + return /\w/.test(ch) || ch.toUpperCase() != ch.toLowerCase(); + } + + // See if "".split is the broken IE version, if so, provide an + // alternative way to split lines. + var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) { + var pos = 0, nl, result = []; + while ((nl = string.indexOf("\n", pos)) > -1) { + result.push(string.slice(pos, string.charAt(nl-1) == "\r" ? nl - 1 : nl)); + pos = nl + 1; + } + result.push(string.slice(pos)); + return result; + } : function(string){return string.split(/\r?\n/);}; + CodeMirror.splitLines = splitLines; + + var hasSelection = window.getSelection ? function(te) { + try { return te.selectionStart != te.selectionEnd; } + catch(e) { return false; } + } : function(te) { + try {var range = te.ownerDocument.selection.createRange();} + catch(e) {} + if (!range || range.parentElement() != te) return false; + return range.compareEndPoints("StartToEnd", range) != 0; + }; + + CodeMirror.defineMode("null", function() { + return {token: function(stream) {stream.skipToEnd();}}; + }); + CodeMirror.defineMIME("text/plain", "null"); + + var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", + 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", + 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", + 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 186: ";", 187: "=", 188: ",", + 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'", 63276: "PageUp", + 63277: "PageDown", 63275: "End", 63273: "Home", 63234: "Left", 63232: "Up", 63235: "Right", + 63233: "Down", 63302: "Insert", 63272: "Delete"}; + CodeMirror.keyNames = keyNames; + (function() { + // Number keys + for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i); + // Alphabetic keys + for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); + // Function keys + for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; + })(); + + return CodeMirror; +})(); diff --git a/node_modules/esprima/assets/codemirror/javascript.js b/node_modules/esprima/assets/codemirror/javascript.js new file mode 100644 index 000000000..b9388bc9e --- /dev/null +++ b/node_modules/esprima/assets/codemirror/javascript.js @@ -0,0 +1,360 @@ +CodeMirror.defineMode("javascript", function(config, parserConfig) { + var indentUnit = config.indentUnit; + var jsonMode = parserConfig.json; + + // Tokenizer + + var keywords = function(){ + function kw(type) {return {type: type, style: "keyword"};} + var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"); + var operator = kw("operator"), atom = {type: "atom", style: "atom"}; + return { + "if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, + "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, + "var": kw("var"), "const": kw("var"), "let": kw("var"), + "function": kw("function"), "catch": kw("catch"), + "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), + "in": operator, "typeof": operator, "instanceof": operator, + "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom + }; + }(); + + var isOperatorChar = /[+\-*&%=<>!?|]/; + + function chain(stream, state, f) { + state.tokenize = f; + return f(stream, state); + } + + function nextUntilUnescaped(stream, end) { + var escaped = false, next; + while ((next = stream.next()) != null) { + if (next == end && !escaped) + return false; + escaped = !escaped && next == "\\"; + } + return escaped; + } + + // Used as scratch variables to communicate multiple values without + // consing up tons of objects. + var type, content; + function ret(tp, style, cont) { + type = tp; content = cont; + return style; + } + + function jsTokenBase(stream, state) { + var ch = stream.next(); + if (ch == '"' || ch == "'") + return chain(stream, state, jsTokenString(ch)); + else if (/[\[\]{}\(\),;\:\.]/.test(ch)) + return ret(ch); + else if (ch == "0" && stream.eat(/x/i)) { + stream.eatWhile(/[\da-f]/i); + return ret("number", "number"); + } + else if (/\d/.test(ch)) { + stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/); + return ret("number", "number"); + } + else if (ch == "/") { + if (stream.eat("*")) { + return chain(stream, state, jsTokenComment); + } + else if (stream.eat("/")) { + stream.skipToEnd(); + return ret("comment", "comment"); + } + else if (state.reAllowed) { + nextUntilUnescaped(stream, "/"); + stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla + return ret("regexp", "string-2"); + } + else { + stream.eatWhile(isOperatorChar); + return ret("operator", null, stream.current()); + } + } + else if (ch == "#") { + stream.skipToEnd(); + return ret("error", "error"); + } + else if (isOperatorChar.test(ch)) { + stream.eatWhile(isOperatorChar); + return ret("operator", null, stream.current()); + } + else { + stream.eatWhile(/[\w\$_]/); + var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; + return (known && state.kwAllowed) ? ret(known.type, known.style, word) : + ret("variable", "variable", word); + } + } + + function jsTokenString(quote) { + return function(stream, state) { + if (!nextUntilUnescaped(stream, quote)) + state.tokenize = jsTokenBase; + return ret("string", "string"); + }; + } + + function jsTokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = jsTokenBase; + break; + } + maybeEnd = (ch == "*"); + } + return ret("comment", "comment"); + } + + // Parser + + var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true}; + + function JSLexical(indented, column, type, align, prev, info) { + this.indented = indented; + this.column = column; + this.type = type; + this.prev = prev; + this.info = info; + if (align != null) this.align = align; + } + + function inScope(state, varname) { + for (var v = state.localVars; v; v = v.next) + if (v.name == varname) return true; + } + + function parseJS(state, style, type, content, stream) { + var cc = state.cc; + // Communicate our context to the combinators. + // (Less wasteful than consing up a hundred closures on every call.) + cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; + + if (!state.lexical.hasOwnProperty("align")) + state.lexical.align = true; + + while(true) { + var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement; + if (combinator(type, content)) { + while(cc.length && cc[cc.length - 1].lex) + cc.pop()(); + if (cx.marked) return cx.marked; + if (type == "variable" && inScope(state, content)) return "variable-2"; + return style; + } + } + } + + // Combinator utils + + var cx = {state: null, column: null, marked: null, cc: null}; + function pass() { + for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]); + } + function cont() { + pass.apply(null, arguments); + return true; + } + function register(varname) { + var state = cx.state; + if (state.context) { + cx.marked = "def"; + for (var v = state.localVars; v; v = v.next) + if (v.name == varname) return; + state.localVars = {name: varname, next: state.localVars}; + } + } + + // Combinators + + var defaultVars = {name: "this", next: {name: "arguments"}}; + function pushcontext() { + if (!cx.state.context) cx.state.localVars = defaultVars; + cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; + } + function popcontext() { + cx.state.localVars = cx.state.context.vars; + cx.state.context = cx.state.context.prev; + } + function pushlex(type, info) { + var result = function() { + var state = cx.state; + state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info) + }; + result.lex = true; + return result; + } + function poplex() { + var state = cx.state; + if (state.lexical.prev) { + if (state.lexical.type == ")") + state.indented = state.lexical.indented; + state.lexical = state.lexical.prev; + } + } + poplex.lex = true; + + function expect(wanted) { + return function expecting(type) { + if (type == wanted) return cont(); + else if (wanted == ";") return pass(); + else return cont(arguments.callee); + }; + } + + function statement(type) { + if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex); + if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex); + if (type == "keyword b") return cont(pushlex("form"), statement, poplex); + if (type == "{") return cont(pushlex("}"), block, poplex); + if (type == ";") return cont(); + if (type == "function") return cont(functiondef); + if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), + poplex, statement, poplex); + if (type == "variable") return cont(pushlex("stat"), maybelabel); + if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), + block, poplex, poplex); + if (type == "case") return cont(expression, expect(":")); + if (type == "default") return cont(expect(":")); + if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), + statement, poplex, popcontext); + return pass(pushlex("stat"), expression, expect(";"), poplex); + } + function expression(type) { + if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator); + if (type == "function") return cont(functiondef); + if (type == "keyword c") return cont(maybeexpression); + if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator); + if (type == "operator") return cont(expression); + if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator); + if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator); + return cont(); + } + function maybeexpression(type) { + if (type.match(/[;\}\)\],]/)) return pass(); + return pass(expression); + } + + function maybeoperator(type, value) { + if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator); + if (type == "operator") return cont(expression); + if (type == ";") return; + if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator); + if (type == ".") return cont(property, maybeoperator); + if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator); + } + function maybelabel(type) { + if (type == ":") return cont(poplex, statement); + return pass(maybeoperator, expect(";"), poplex); + } + function property(type) { + if (type == "variable") {cx.marked = "property"; return cont();} + } + function objprop(type) { + if (type == "variable") cx.marked = "property"; + if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression); + } + function commasep(what, end) { + function proceed(type) { + if (type == ",") return cont(what, proceed); + if (type == end) return cont(); + return cont(expect(end)); + } + return function commaSeparated(type) { + if (type == end) return cont(); + else return pass(what, proceed); + }; + } + function block(type) { + if (type == "}") return cont(); + return pass(statement, block); + } + function vardef1(type, value) { + if (type == "variable"){register(value); return cont(vardef2);} + return cont(); + } + function vardef2(type, value) { + if (value == "=") return cont(expression, vardef2); + if (type == ",") return cont(vardef1); + } + function forspec1(type) { + if (type == "var") return cont(vardef1, forspec2); + if (type == ";") return pass(forspec2); + if (type == "variable") return cont(formaybein); + return pass(forspec2); + } + function formaybein(type, value) { + if (value == "in") return cont(expression); + return cont(maybeoperator, forspec2); + } + function forspec2(type, value) { + if (type == ";") return cont(forspec3); + if (value == "in") return cont(expression); + return cont(expression, expect(";"), forspec3); + } + function forspec3(type) { + if (type != ")") cont(expression); + } + function functiondef(type, value) { + if (type == "variable") {register(value); return cont(functiondef);} + if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext); + } + function funarg(type, value) { + if (type == "variable") {register(value); return cont();} + } + + // Interface + + return { + startState: function(basecolumn) { + return { + tokenize: jsTokenBase, + reAllowed: true, + kwAllowed: true, + cc: [], + lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), + localVars: null, + context: null, + indented: 0 + }; + }, + + token: function(stream, state) { + if (stream.sol()) { + if (!state.lexical.hasOwnProperty("align")) + state.lexical.align = false; + state.indented = stream.indentation(); + } + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + if (type == "comment") return style; + state.reAllowed = type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/); + state.kwAllowed = type != '.'; + return parseJS(state, style, type, content, stream); + }, + + indent: function(state, textAfter) { + if (state.tokenize != jsTokenBase) return 0; + var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, + type = lexical.type, closing = firstChar == type; + if (type == "vardef") return lexical.indented + 4; + else if (type == "form" && firstChar == "{") return lexical.indented; + else if (type == "stat" || type == "form") return lexical.indented + indentUnit; + else if (lexical.info == "switch" && !closing) + return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); + else if (lexical.align) return lexical.column + (closing ? 0 : 1); + else return lexical.indented + (closing ? 0 : indentUnit); + }, + + electricChars: ":{}" + }; +}); + +CodeMirror.defineMIME("text/javascript", "javascript"); +CodeMirror.defineMIME("application/json", {name: "javascript", json: true}); diff --git a/node_modules/esprima/assets/json2.js b/node_modules/esprima/assets/json2.js new file mode 100644 index 000000000..5e2f299eb --- /dev/null +++ b/node_modules/esprima/assets/json2.js @@ -0,0 +1,487 @@ +/* + http://www.JSON.org/json2.js + 2011-10-19 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + See http://www.JSON.org/js.html + + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. + + + This file creates a global JSON object containing two methods: stringify + and parse. + + JSON.stringify(value, replacer, space) + value any JavaScript value, usually an object or array. + + replacer an optional parameter that determines how object + values are stringified for objects. It can be a + function or an array of strings. + + space an optional parameter that specifies the indentation + of nested structures. If it is omitted, the text will + be packed without extra whitespace. If it is a number, + it will specify the number of spaces to indent at each + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. + + This method produces a JSON text from a JavaScript value. + + When an object value is found, if the object contains a toJSON + method, its toJSON method will be called and the result will be + stringified. A toJSON method does not serialize: it returns the + value represented by the name/value pair that should be serialized, + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the value + + For example, this would serialize Dates as ISO strings. + + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + return this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + You can provide an optional replacer method. It will be passed the + key and value of each member, with this bound to the containing + object. The value that is returned from your method will be + serialized. If your method returns undefined, then the member will + be excluded from the serialization. + + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are + stringified. + + Values that do not have JSON representations, such as undefined or + functions, will not be serialized. Such values in objects will be + dropped; in arrays they will be replaced with null. You can use + a replacer function to replace those with JSON values. + JSON.stringify(undefined) returns undefined. + + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. + + If the space parameter is a non-empty string, then that string will + be used for indentation. If the space parameter is a number, then + the indentation will be that many spaces. + + Example: + + text = JSON.stringify(['e', {pluribus: 'unum'}]); + // text is '["e",{"pluribus":"unum"}]' + + + text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); + // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + + + JSON.parse(text, reviver) + This method parses a JSON text to produce an object or array. + It can throw a SyntaxError exception. + + The optional reviver parameter is a function that can filter and + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. + + Example: + + // Parse the text. Values that look like ISO date strings will + // be converted to Date objects. + + myData = JSON.parse(text, function (key, value) { + var a; + if (typeof value === 'string') { + a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); + if (a) { + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], + +a[5], +a[6])); + } + } + return value; + }); + + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); + + + This is a reference implementation. You are free to copy, modify, or + redistribute. +*/ + +/*jslint evil: true, regexp: true */ + +/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf +*/ + + +// Create a JSON object only if one does not already exist. We create the +// methods in a closure to avoid creating global variables. + +var JSON; +if (!JSON) { + JSON = {}; +} + +(function () { + 'use strict'; + + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + if (typeof Date.prototype.toJSON !== 'function') { + + Date.prototype.toJSON = function (key) { + + return isFinite(this.valueOf()) + ? this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' + : null; + }; + + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = function (key) { + return this.valueOf(); + }; + } + + var cx = new RegExp('/[\u0000\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]', 'g'), + escapable = new RegExp('/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]', 'g'), + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; + + + function quote(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + + escapable.lastIndex = 0; + return escapable.test(string) ? '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' + ? c + : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : '"' + string + '"'; + } + + + function str(key, holder) { + +// Produce a string from holder[key]. + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + +// If the value has a toJSON method, call it to obtain a replacement value. + + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. + + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } + +// What happens next depends on the value's type. + + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + +// If the value is a boolean or null, convert it to a string. Note: +// typeof null does not produce 'null'. The case is included here in +// the remote chance that this gets fixed someday. + + return String(value); + +// If the type is 'object', we might be dealing with an object or an array or +// null. + + case 'object': + +// Due to a specification blunder in ECMAScript, typeof null is 'object', +// so watch out for that case. + + if (!value) { + return 'null'; + } + +// Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + +// Is the value an array? + + if (Object.prototype.toString.apply(value) === '[object Array]') { + +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + +// Join all of the elements together, separated with commas, and wrap them in +// brackets. + + v = partial.length === 0 + ? '[]' + : gap + ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' + : '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + +// If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + if (typeof rep[i] === 'string') { + k = rep[i]; + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } else { + +// Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } + +// Join all of the member texts together, separated with commas, +// and wrap them in braces. + + v = partial.length === 0 + ? '{}' + : gap + ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' + : '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + } + +// If the JSON object does not yet have a stringify method, give it one. + + if (typeof JSON.stringify !== 'function') { + JSON.stringify = function (value, replacer, space) { + +// The stringify method takes a value and an optional replacer, and an optional +// space parameter, and returns a JSON text. The replacer can be a function +// that can replace values, or an array of strings that will select the keys. +// A default replacer method can be provided. Use of the space parameter can +// produce text that is more easily readable. + + var i; + gap = ''; + indent = ''; + +// If the space parameter is a number, make an indent string containing that +// many spaces. + + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; + } + +// If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === 'string') { + indent = space; + } + +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } + +// Make a fake root object containing our value under the key of ''. +// Return the result of stringifying the value. + + return str('', {'': value}); + }; + } + + +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== 'function') { + JSON.parse = function (text, reviver) { + +// The parse method takes a text and an optional reviver function, and returns +// a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + +// The walk method is used to recursively walk the resulting structure so +// that modifications can be made. + + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. + + text = String(text); + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with '()' and 'new' +// because they can cause invocation, and '=' because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around +// crippling inefficiencies in IE's and Safari's regexp engines. First we +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we +// replace all simple value tokens with ']' characters. Third, we delete all +// open brackets that follow a colon or comma or that begin the text. Finally, +// we look to see that the remaining characters are only whitespace or ']' or +// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + if (/^[\],:{}\s]*$/ + .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') + .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + +// In the third stage we use the eval function to compile the text into a +// JavaScript structure. The '{' operator is subject to a syntactic ambiguity +// in JavaScript: it can begin a block or an object literal. We wrap the text +// in parens to eliminate the ambiguity. + + j = eval('(' + text + ')'); + +// In the optional fourth stage, we recursively walk the new structure, passing +// each name/value pair to a reviver function for possible transformation. + + return typeof reviver === 'function' + ? walk({'': j}, '') + : j; + } + +// If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError('JSON.parse'); + }; + } +}()); diff --git a/node_modules/esprima/assets/style.css b/node_modules/esprima/assets/style.css new file mode 100644 index 000000000..9ff8c6981 --- /dev/null +++ b/node_modules/esprima/assets/style.css @@ -0,0 +1,164 @@ +body { + background-color: #ffffff; + min-width: 960px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 13px; + font-weight: normal; + line-height: 18px; + color: #444; + margin: 0; +} + +p { + font-size: 13px; + font-weight: normal; + line-height: 18px; + margin-bottom: 9px; +} + +ul { + padding-left: 20px; +} + +li { + font-size: 13px; + font-weight: normal; + line-height: 18px; + margin-bottom: 4px; +} + +pre { + margin-left: 15px; + font-family: Inconsolata, Menlo, 'Courier New', monospace; +} + +h1 { + margin-top: 2px; + margin-bottom: 18px; + font-size: 30px; + line-height: 36px; + font-weight: bold; + color: #444; +} + +h1 small { + font-size: 18px; + color: #ccc; +} + +h3, h4 { + font-size: 18px; + font-weight: bold; + color: #444; +} + +h4 { + font-size: 16px; +} + +.code { + border-left: 3px solid #ddd; + margin: 5px 3px 8px 15px; + padding: 0 0 0 8px; +} + +.CodeMirror { + padding: 0; + border: 1px solid #ccc; +} + +.CodeMirror-scroll { + height: 200px; +} + +.container { + margin-left: auto; + margin-right: auto; + width: 960px; +} + +.topbar { + top: 0; + margin: 0 0 0 480px; + padding: 0; + width: 300px; +} + +.topbar ul { + padding: 5px 20px 5px 10px; + background-color: #222; + margin: 0 10px 0 0; +} + +.topbar a { + color: #ddd; + text-decoration: none; +} + +.topbar a:hover { + color: #fff; +} + +.topbar .nav li { + background-color: #222; + display: inline; + padding: 4px 10px 4px 0px; + margin: 0; +} + +.container .main { + width: 540px; + display: inline; + float: left; + margin-left: 10px; + margin-right: 10px; +} + +.container .sidebar { + width: 350px; + display: inline; + float: left; + margin-left: 30px; + margin-right: 10px; +} + +.footer { + margin-top: 25px; + margin-bottom: 25px; + color: #555; + text-align: center; +} + +textarea { + font-family: Inconsolata, Monaco, Consolas, "Lucida Console", monospace; + font-size: 13px; + color: #555; + width: 80%; + padding: 7px; + overflow: auto; +} + +table { + width: 80%; + border: 1px solid #ccc; + border-spacing: 0; +} + +thead { + font-weight: bold; +} + +table th, table td { + border-left: 1px solid #ccc; + padding: 3px 3px 10px 3px; + text-align: center; +} + +table td { + border-top: 1px solid #ccc; +} + +tbody tr:nth-child(odd) td { + background-color: #eee; +} + diff --git a/node_modules/esprima/bin/esparse.js b/node_modules/esprima/bin/esparse.js new file mode 100755 index 000000000..9f798947f --- /dev/null +++ b/node_modules/esprima/bin/esparse.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +/* + Copyright (C) 2011 Ariya Hidayat + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/*jslint sloppy:true node:true */ + +var fs = require('fs'), + esprima = require('esprima'), + files = process.argv.splice(2); + +if (files.length === 0) { + console.log('Usage:'); + console.log(' esparse file.js'); + process.exit(1); +} + +files.forEach(function (filename) { + var content = fs.readFileSync(filename, 'utf-8'); + console.log(JSON.stringify(esprima.parse(content), null, 4)); +}); +/* vim: set sw=4 ts=4 et tw=80 : */ diff --git a/node_modules/esprima/demo/checkenv.js b/node_modules/esprima/demo/checkenv.js new file mode 100644 index 000000000..b124442bc --- /dev/null +++ b/node_modules/esprima/demo/checkenv.js @@ -0,0 +1,42 @@ +/* + Copyright (C) 2011 Ariya Hidayat + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// Unfortunately we have to use User Agent detection to blacklist the browsers. +/*jslint browser:true */ +(function (window) { + 'use strict'; + + var majorVersion = parseInt(window.platform.version.split('.')[0], 10); + + window.checkEnv = function () { + if (window.platform.name === 'Safari' && majorVersion <= 3) { + throw new Error('CodeMirror does not support Safari <= 3'); + } + if (window.platform.name === 'Opera' && majorVersion <= 8) { + throw new Error('CodeMirror does not support Opera <= 8'); + } + }; + +}(window)); +/* vim: set sw=4 ts=4 et tw=80 : */ diff --git a/node_modules/esprima/demo/collector.html b/node_modules/esprima/demo/collector.html new file mode 100644 index 000000000..ad28feac8 --- /dev/null +++ b/node_modules/esprima/demo/collector.html @@ -0,0 +1,82 @@ + + + + +Esprima: Regex Collector Demo + + + + + + + + + + +
+ + + +

Regular Expression Collector uncovers all your secrets

+ +

Note: Only regular expression literals and objects created with RegExp are considered.

+

Type ECMAScript code:

+

+

The above code editor is based on CodeMirror.

+ +

Using Esprima version .

+ +
+
+ + + + +
+ + + diff --git a/node_modules/esprima/demo/collector.js b/node_modules/esprima/demo/collector.js new file mode 100644 index 000000000..75a523bc4 --- /dev/null +++ b/node_modules/esprima/demo/collector.js @@ -0,0 +1,170 @@ +/* + Copyright (C) 2011 Ariya Hidayat + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/*jslint browser:true */ + +var timerId; + +function collectRegex() { + 'use strict'; + + function id(i) { + return document.getElementById(i); + } + + function escaped(str) { + return str.replace(/&/g, "&").replace(//g, ">"); + } + + function setText(id, str) { + var el = document.getElementById(id); + if (typeof el.innerText === 'string') { + el.innerText = str; + } else { + el.textContent = str; + } + } + + function isLineTerminator(ch) { + return (ch === '\n' || ch === '\r' || ch === '\u2028' || ch === '\u2029'); + } + + function process(delay) { + if (timerId) { + window.clearTimeout(timerId); + } + + timerId = window.setTimeout(function () { + var code, result, i, str; + + if (typeof window.editor === 'undefined') { + code = document.getElementById('code').value; + } else { + code = window.editor.getValue(); + } + + // Executes f on the object and its children (recursively). + function visit(object, f) { + var key, child; + + if (f.call(null, object) === false) { + return; + } + for (key in object) { + if (object.hasOwnProperty(key)) { + child = object[key]; + if (typeof child === 'object' && child !== null) { + visit(child, f); + } + } + } + } + + function createRegex(pattern, mode) { + var literal; + try { + literal = new RegExp(pattern, mode); + } catch (e) { + // Invalid regular expression. + return; + } + return literal; + } + + function collect(node) { + var str, arg, value; + if (node.type === 'Literal') { + if (node.value instanceof RegExp) { + str = node.value.toString(); + if (str[0] === '/') { + result.push({ + type: 'Literal', + value: node.value, + line: node.loc.start.line, + column: node.loc.start.column + }); + } + } + } + if (node.type === 'NewExpression' || node.type === 'CallExpression') { + if (node.callee.type === 'Identifier' && node.callee.name === 'RegExp') { + arg = node['arguments']; + if (arg.length === 1 && arg[0].type === 'Literal') { + if (typeof arg[0].value === 'string') { + value = createRegex(arg[0].value); + if (value) { + result.push({ + type: 'Literal', + value: value, + line: node.loc.start.line, + column: node.loc.start.column + }); + } + } + } + if (arg.length === 2 && arg[0].type === 'Literal' && arg[1].type === 'Literal') { + if (typeof arg[0].value === 'string' && typeof arg[1].value === 'string') { + value = createRegex(arg[0].value, arg[1].value); + if (value) { + result.push({ + type: 'Literal', + value: value, + line: node.loc.start.line, + column: node.loc.start.column + }); + } + } + } + } + } + } + + try { + result = []; + visit(window.esprima.parse(code, { loc: true }), collect); + + if (result.length > 0) { + str = '

Found ' + result.length + ' regex(s):

'; + for (i = 0; i < result.length; i += 1) { + str += '

' + 'Line ' + result[i].line; + str += ' column ' + (1 + result[i].column); + str += ': '; str += escaped(result[i].value.toString()) + ''; + str += '

'; + } + id('result').innerHTML = str; + } else { + setText('result', 'No regex.'); + } + } catch (e) { + setText('result', e.toString()); + } + + timerId = undefined; + }, delay || 811); + } + + setText('version', window.esprima.version); + process(1); +} +/* vim: set sw=4 ts=4 et tw=80 : */ diff --git a/node_modules/esprima/demo/functiontrace.html b/node_modules/esprima/demo/functiontrace.html new file mode 100644 index 000000000..177ee0e25 --- /dev/null +++ b/node_modules/esprima/demo/functiontrace.html @@ -0,0 +1,105 @@ + + + + +Esprima: Function Trace Demo + + + + + + + + + + + +
+ + + +

Function Trace reveals what is being called

+ +

Type ECMAScript code:

+

+

The above code editor is based on CodeMirror.

+ +

+ +

No result yet.

+ +

Using Esprima version .

+ + + +
+ + + diff --git a/node_modules/esprima/demo/functiontrace.js b/node_modules/esprima/demo/functiontrace.js new file mode 100644 index 000000000..4f2bedb8c --- /dev/null +++ b/node_modules/esprima/demo/functiontrace.js @@ -0,0 +1,123 @@ +/* + Copyright (C) 2012 Ariya Hidayat + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/*jslint browser:true evil:true */ + +var timerId; + +function traceRun() { + 'use strict'; + + var lookup; + + function id(i) { + return document.getElementById(i); + } + + function escaped(str) { + return str.replace(/&/g, "&").replace(//g, ">"); + } + + function setText(id, str) { + var el = document.getElementById(id); + if (typeof el.innerText === 'string') { + el.innerText = str; + } else { + el.textContent = str; + } + } + + function isLineTerminator(ch) { + return (ch === '\n' || ch === '\r' || ch === '\u2028' || ch === '\u2029'); + } + + function insertTracer() { + var tracer, code, i, functionList, signature, pos; + + if (typeof window.editor === 'undefined') { + code = document.getElementById('code').value; + } else { + code = window.editor.getValue(); + } + + tracer = window.esprima.Tracer.FunctionEntrance('window.TRACE.enterFunction'); + code = window.esprima.modify(code, tracer); + + // Enclose in IIFE. + code = '(function() {\n' + code + '\n}())'; + + return code; + } + + function showResult() { + var i, str, histogram, entry; + + histogram = window.TRACE.getHistogram(); + + str = ''; + for (i = 0; i < histogram.length; i += 1) { + entry = histogram[i]; + str += ''; + str += ''; + str += ''; + str += ''; + } + str += '
FunctionHits
' + entry.name + '' + entry.count + '
'; + + id('result').innerHTML = str; + } + + window.TRACE = { + hits: {}, + enterFunction: function (info) { + var key = info.name + ' at line ' + info.lineNumber; + if (this.hits.hasOwnProperty(key)) { + this.hits[key] = this.hits[key] + 1; + } else { + this.hits[key] = 1; + } + }, + getHistogram: function () { + var entry, + sorted = []; + for (entry in this.hits) { + if (this.hits.hasOwnProperty(entry)) { + sorted.push({ name: entry, count: this.hits[entry]}); + } + } + sorted.sort(function (a, b) { + return b.count - a.count; + }); + return sorted; + } + }; + + try { + eval(insertTracer()); + showResult(); + } catch (e) { + id('result').innerText = e.toString(); + } +} +/* vim: set sw=4 ts=4 et tw=80 : */ diff --git a/node_modules/esprima/demo/parse.css b/node_modules/esprima/demo/parse.css new file mode 100644 index 000000000..fe9cf83ff --- /dev/null +++ b/node_modules/esprima/demo/parse.css @@ -0,0 +1,82 @@ +.tabs textarea { + padding: 7px 0px 7px 7px; +} + +.tabs { + position: relative; + display: block; + margin-top: 30px; + height: 1px; +} + +.tabs ul, .tabs li { + list-style-type: none; + margin: 0; + line-height: 0px; +} + +.tabs h3 { + float: left; + position: relative; + margin: 0 6px 0 0; + border: 1px solid #bbb; + background-color: #eee; + border-bottom: none; + cursor: pointer; + z-index: 0; + -moz-border-radius-topleft: 6px; + -webkit-border-top-left-radius: 6px; + -ms-border-top-left-radius: 6px; + -o-border-top-left-radius: 6px; + border-top-left-radius: 6px; + -moz-border-radius-topright: 6px; + -webkit-border-top-right-radius: 6px; + -ms-border-top-right-radius: 6px; + -o-border-top-right-radius: 6px; + border-top-right-radius: 6px +} + +.tabs .active h3 { + background-color: #fff; + border-bottom-color: #fff; + z-index: 2 +} + +.tabs h3 a { + padding: 0 10px; + line-height: 29px; + font-size: 13px; + font-weight: normal; +} + +.tabs a { + color: black; + text-decoration: none; +} + +.tabs a:visited { + color: black; +} + +.tabs .tab { + position: absolute; + display: none; + left: 0; + top: 29px; + right: 0; + border-top: 1px solid #bbb; + z-index: 1; + padding: 25px 60px 50px 5px; +} + +.pages div { + display: none; +} + +.pages div.active { + display: block; +} + +.tabs .active .tab { + display: block; +} diff --git a/node_modules/esprima/demo/parse.html b/node_modules/esprima/demo/parse.html new file mode 100644 index 000000000..c243f8687 --- /dev/null +++ b/node_modules/esprima/demo/parse.html @@ -0,0 +1,190 @@ + + + + +Esprima: Parsing Demo + + + + + + + + + + + + +
+ + + +

Parser produces syntax tree

+ +

Esprima version .

+ +

Type ECMAScript code:

+

+

The above code editor is based on CodeMirror.

+

+

+

Syntax node location info (start, end)

+
+

+

+
+
+ +
+ + + + +

+
+ + + + diff --git a/node_modules/esprima/demo/precedence.html b/node_modules/esprima/demo/precedence.html new file mode 100644 index 000000000..0b1b6e01e --- /dev/null +++ b/node_modules/esprima/demo/precedence.html @@ -0,0 +1,225 @@ + + + + +Esprima: Operator Precedence Demo + + + + + + + +
+ + + +

Operator precedence is not always easy

+

Is 1 << 2 * 3 semantically equivalent to +(1 << 2) * 3?

+

+

This demo is inspired by mothereff.in/operator-precedence.

+ +
+ + + diff --git a/node_modules/esprima/esprima.js b/node_modules/esprima/esprima.js new file mode 100644 index 000000000..b51e59910 --- /dev/null +++ b/node_modules/esprima/esprima.js @@ -0,0 +1,4202 @@ +/* + Copyright (C) 2012 Ariya Hidayat + Copyright (C) 2012 Joost-Wim Boekesteijn + Copyright (C) 2012 Kris Kowal + Copyright (C) 2012 Yusuke Suzuki + Copyright (C) 2012 Arpad Borsos + Copyright (C) 2011 Ariya Hidayat + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/*jslint bitwise:true */ +/*global esprima:true, exports:true, +throwError: true, createLiteral: true, generateStatement: true, +parseAssignmentExpression: true, parseBlock: true, parseExpression: true, +parseFunctionDeclaration: true, parseFunctionExpression: true, +parseLeftHandSideExpression: true, +parseStatement: true, parseSourceElement: true */ + +(function (exports) { + 'use strict'; + + var Token, + Syntax, + PropertyKind, + Messages, + Regex, + Precedence, + BinaryPrecedence, + source, + allowIn, + strict, + index, + lineNumber, + lineStart, + length, + buffer, + base, + indent, + extra; + + Token = { + BooleanLiteral: 1, + EOF: 2, + Identifier: 3, + Keyword: 4, + NullLiteral: 5, + NumericLiteral: 6, + Punctuator: 7, + StringLiteral: 8 + }; + + Syntax = { + AssignmentExpression: 'AssignmentExpression', + ArrayExpression: 'ArrayExpression', + BlockStatement: 'BlockStatement', + BinaryExpression: 'BinaryExpression', + BreakStatement: 'BreakStatement', + CallExpression: 'CallExpression', + CatchClause: 'CatchClause', + ConditionalExpression: 'ConditionalExpression', + ContinueStatement: 'ContinueStatement', + DoWhileStatement: 'DoWhileStatement', + DebuggerStatement: 'DebuggerStatement', + EmptyStatement: 'EmptyStatement', + ExpressionStatement: 'ExpressionStatement', + ForStatement: 'ForStatement', + ForInStatement: 'ForInStatement', + FunctionDeclaration: 'FunctionDeclaration', + FunctionExpression: 'FunctionExpression', + Identifier: 'Identifier', + IfStatement: 'IfStatement', + Literal: 'Literal', + LabeledStatement: 'LabeledStatement', + LogicalExpression: 'LogicalExpression', + MemberExpression: 'MemberExpression', + NewExpression: 'NewExpression', + ObjectExpression: 'ObjectExpression', + Program: 'Program', + Property: 'Property', + ReturnStatement: 'ReturnStatement', + SequenceExpression: 'SequenceExpression', + SwitchStatement: 'SwitchStatement', + SwitchCase: 'SwitchCase', + ThisExpression: 'ThisExpression', + ThrowStatement: 'ThrowStatement', + TryStatement: 'TryStatement', + UnaryExpression: 'UnaryExpression', + UpdateExpression: 'UpdateExpression', + VariableDeclaration: 'VariableDeclaration', + VariableDeclarator: 'VariableDeclarator', + WhileStatement: 'WhileStatement', + WithStatement: 'WithStatement' + }; + + PropertyKind = { + Data: 1, + Get: 2, + Set: 4 + }; + + Messages = { + UnexpectedToken: 'Unexpected token %0', + UnexpectedNumber: 'Unexpected number', + UnexpectedString: 'Unexpected string', + UnexpectedIdentifier: 'Unexpected identifier', + UnexpectedReserved: 'Unexpected reserved word', + UnexpectedEOS: 'Unexpected end of input', + NewlineAfterThrow: 'Illegal newline after throw', + InvalidRegExp: 'Invalid regular expression', + UnterminatedRegExp: 'Invalid regular expression: missing /', + InvalidLHSInAssignment: 'Invalid left-hand side in assignment', + InvalidLHSInForIn: 'Invalid left-hand side in for-in', + InvalidLHSInPostfixOp: 'Invalid left-hand side expression in postfix operation', + InvalidLHSInPrefixOp: 'Invalid left-hand side expression in prefix operation', + NoCatchOrFinally: 'Missing catch or finally after try', + StrictDelete: 'Delete of an unqualified identifier in strict mode.', + StrictDuplicateProperty: 'Duplicate data property in object literal not allowed in strict mode', + StrictAccessorDataProperty: 'Object literal may not have data and accessor property with the same name', + StrictAccessorGetSet: 'Object literal may not have multiple get/set accessors with the same name' + }; + + Precedence = { + Sequence: 0, + Assignment: 1, + Conditional: 2, + LogicalOR: 3, + LogicalAND: 4, + LogicalXOR: 5, + BitwiseOR: 6, + BitwiseAND: 7, + Equality: 8, + Relational: 9, + BitwiseSHIFT: 10, + Additive: 11, + Multiplicative: 12, + Unary: 13, + Postfix: 14, + Call: 15, + New: 16, + Member: 17, + Primary: 18 + }; + + // See also tools/generate-unicode-regex.py. + Regex = { + NonAsciiIdentifierStart: new RegExp('[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376-\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E-\u066F\u0671-\u06D3\u06D5\u06E5-\u06E6\u06EE-\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4-\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097F\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC-\u09DD\u09DF-\u09E1\u09F0-\u09F1\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0-\u0AE1\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58-\u0C59\u0C60-\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0-\u0CE1\u0CF1-\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32-\u0E33\u0E40-\u0E46\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB2-\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDD\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065-\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE-\u1BAF\u1BC0-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183-\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3006\u3031-\u3035\u303B-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCB\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A-\uA62B\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA791\uA7A0-\uA7A9\uA7FA-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5-\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA2D\uFA30-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]'), + NonAsciiIdentifierPart: new RegExp('[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376-\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u0900-\u0963\u0966-\u096F\u0971-\u0977\u0979-\u097F\u0981-\u0983\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7-\u09C8\u09CB-\u09CE\u09D7\u09DC-\u09DD\u09DF-\u09E3\u09E6-\u09F1\u0A01-\u0A03\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A3C\u0A3E-\u0A42\u0A47-\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0B01-\u0B03\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47-\u0B48\u0B4B-\u0B4D\u0B56-\u0B57\u0B5C-\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82-\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C01-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55-\u0C56\u0C58-\u0C59\u0C60-\u0C63\u0C66-\u0C6F\u0C82-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5-\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1-\u0CF2\u0D02-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D57\u0D60-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82-\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2-\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDD\u0F00\u0F18-\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772-\u1773\u1780-\u17B3\u17B6-\u17D3\u17D7\u17DC-\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191C\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19D9\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BAA\u1BAE-\u1BB9\u1BC0-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1CD0-\u1CD2\u1CD4-\u1CF2\u1D00-\u1DE6\u1DFC-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u203F-\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183-\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF1\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u2E2F\u3005-\u3006\u302A-\u302F\u3031-\u3035\u303B-\u303C\u3041-\u3096\u3099-\u309A\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCB\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA67C-\uA67D\uA67F-\uA697\uA6A0-\uA6E5\uA6F0-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA791\uA7A0-\uA7A9\uA7FA-\uA827\uA840-\uA873\uA880-\uA8C4\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A-\uAA7B\uAA80-\uAAC2\uAADB-\uAADD\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABEA\uABEC-\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA2D\uFA30-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE26\uFE33-\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]') + }; + + if (typeof Object.freeze === 'function') { + Object.freeze(Token); + Object.freeze(Syntax); + Object.freeze(Messages); + Object.freeze(Regex); + } + + function sliceSource(from, to) { + return source.slice(from, to); + } + + if (typeof 'esprima'[0] === 'undefined') { + sliceSource = function sliceArraySource(from, to) { + return source.slice(from, to).join(''); + }; + } + + function isDecimalDigit(ch) { + return '0123456789'.indexOf(ch) >= 0; + } + + function isHexDigit(ch) { + return '0123456789abcdefABCDEF'.indexOf(ch) >= 0; + } + + function isOctalDigit(ch) { + return '01234567'.indexOf(ch) >= 0; + } + + + // 7.2 White Space + + function isWhiteSpace(ch) { + // TODO Unicode "space separator" + return (ch === ' ') || (ch === '\u0009') || (ch === '\u000B') || + (ch === '\u000C') || (ch === '\u00A0') || (ch === '\uFEFF'); + } + + // 7.3 Line Terminators + + function isLineTerminator(ch) { + return (ch === '\n' || ch === '\r' || ch === '\u2028' || ch === '\u2029'); + } + + // 7.6 Identifier Names and Identifiers + + function isIdentifierStart(ch) { + return (ch === '$') || (ch === '_') || (ch === '\\') || + (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || + ((ch.charCodeAt(0) >= 0x80) && Regex.NonAsciiIdentifierStart.test(ch)); + } + + function isIdentifierPart(ch) { + return (ch === '$') || (ch === '_') || (ch === '\\') || + (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || + ((ch >= '0') && (ch <= '9')) || + ((ch.charCodeAt(0) >= 0x80) && Regex.NonAsciiIdentifierPart.test(ch)); + } + + // 7.6.1.2 Future Reserved Words + + function isFutureReservedWord(id) { + switch (id) { + + // Future reserved words. + case 'class': + case 'enum': + case 'export': + case 'extends': + case 'import': + case 'super': + return true; + } + + return false; + } + + // 7.6.1.1 Keywords + + function isKeyword(id) { + switch (id) { + + // Keywords. + case 'break': + case 'case': + case 'catch': + case 'continue': + case 'debugger': + case 'default': + case 'delete': + case 'do': + case 'else': + case 'finally': + case 'for': + case 'function': + case 'if': + case 'in': + case 'instanceof': + case 'new': + case 'return': + case 'switch': + case 'this': + case 'throw': + case 'try': + case 'typeof': + case 'var': + case 'void': + case 'while': + case 'with': + return true; + + // Future reserved words. + // 'const' is specialized as Keyword in V8. + case 'const': + return true; + + // strict mode + case 'implements': + case 'interface': + case 'let': + case 'package': + case 'private': + case 'protected': + case 'public': + case 'static': + case 'yield': + return true; + } + + return isFutureReservedWord(id); + } + + // Return the next character and move forward. + + function nextChar() { + var ch = '\x00', + idx = index; + if (idx < length) { + ch = source[idx]; + index += 1; + } + return ch; + } + + // 7.4 Comments + + function skipComment() { + var ch, blockComment, lineComment; + + blockComment = false; + lineComment = false; + + while (index < length) { + ch = source[index]; + + if (lineComment) { + nextChar(); + if (isLineTerminator(ch)) { + lineComment = false; + if (ch === '\r' && source[index] === '\n') { + nextChar(); + } + lineNumber += 1; + lineStart = index; + } + } else if (blockComment) { + nextChar(); + if (ch === '*') { + ch = source[index]; + if (ch === '/') { + nextChar(); + blockComment = false; + } + } else if (isLineTerminator(ch)) { + if (ch === '\r' && source[index] === '\n') { + nextChar(); + } + lineNumber += 1; + lineStart = index; + } + } else if (ch === '/') { + ch = source[index + 1]; + if (ch === '/') { + nextChar(); + nextChar(); + lineComment = true; + } else if (ch === '*') { + nextChar(); + nextChar(); + blockComment = true; + } else { + break; + } + } else if (isWhiteSpace(ch)) { + nextChar(); + } else if (isLineTerminator(ch)) { + nextChar(); + if (ch === '\r' && source[index] === '\n') { + nextChar(); + } + lineNumber += 1; + lineStart = index; + } else { + break; + } + } + } + + function scanHexEscape(prefix) { + var i, len, ch, code = 0; + + len = (prefix === 'u') ? 4 : 2; + for (i = 0; i < len; i += 1) { + if (index < length && isHexDigit(source[index])) { + ch = nextChar(); + code = code * 16 + '0123456789abcdef'.indexOf(ch.toLowerCase()); + } else { + return ''; + } + } + return String.fromCharCode(code); + } + + function scanIdentifier() { + var ch, start, id, restore; + + ch = source[index]; + if (!isIdentifierStart(ch)) { + return; + } + + start = index; + if (ch === '\\') { + nextChar(); + if (source[index] !== 'u') { + return; + } + nextChar(); + restore = index; + ch = scanHexEscape('u'); + if (ch) { + if (ch === '\\' || !isIdentifierStart(ch)) { + return; + } + id = ch; + } else { + index = restore; + id = 'u'; + } + } else { + id = nextChar(); + } + + while (index < length) { + ch = source[index]; + if (!isIdentifierPart(ch)) { + break; + } + if (ch === '\\') { + nextChar(); + if (source[index] !== 'u') { + return; + } + nextChar(); + restore = index; + ch = scanHexEscape('u'); + if (ch) { + if (ch === '\\' || !isIdentifierPart(ch)) { + return; + } + id += ch; + } else { + index = restore; + id += 'u'; + } + } else { + id += nextChar(); + } + } + + // There is no keyword or literal with only one character. + // Thus, it must be an identifier. + if (id.length === 1) { + return { + type: Token.Identifier, + value: id, + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + if (isKeyword(id)) { + return { + type: Token.Keyword, + value: id, + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + // 7.8.1 Null Literals + + if (id === 'null') { + return { + type: Token.NullLiteral, + value: id, + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + // 7.8.2 Boolean Literals + + if (id === 'true' || id === 'false') { + return { + type: Token.BooleanLiteral, + value: id, + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + return { + type: Token.Identifier, + value: id, + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + // 7.7 Punctuators + + function scanPunctuator() { + var start = index, + ch1 = source[index], + ch2, + ch3, + ch4; + + // Check for most common single-character punctuators. + + if (ch1 === ';' || ch1 === '{' || ch1 === '}') { + nextChar(); + return { + type: Token.Punctuator, + value: ch1, + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + if (ch1 === ',' || ch1 === '(' || ch1 === ')') { + nextChar(); + return { + type: Token.Punctuator, + value: ch1, + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + // Dot (.) can also start a floating-point number, hence the need + // to check the next character. + + ch2 = source[index + 1]; + if (ch1 === '.' && !isDecimalDigit(ch2)) { + return { + type: Token.Punctuator, + value: nextChar(), + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + // Peek more characters. + + ch3 = source[index + 2]; + ch4 = source[index + 3]; + + // 4-character punctuator: >>>= + + if (ch1 === '>' && ch2 === '>' && ch3 === '>') { + if (ch4 === '=') { + nextChar(); + nextChar(); + nextChar(); + nextChar(); + return { + type: Token.Punctuator, + value: '>>>=', + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + } + + // 3-character punctuators: === !== >>> <<= >>= + + if (ch1 === '=' && ch2 === '=' && ch3 === '=') { + nextChar(); + nextChar(); + nextChar(); + return { + type: Token.Punctuator, + value: '===', + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + if (ch1 === '!' && ch2 === '=' && ch3 === '=') { + nextChar(); + nextChar(); + nextChar(); + return { + type: Token.Punctuator, + value: '!==', + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + if (ch1 === '>' && ch2 === '>' && ch3 === '>') { + nextChar(); + nextChar(); + nextChar(); + return { + type: Token.Punctuator, + value: '>>>', + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + if (ch1 === '<' && ch2 === '<' && ch3 === '=') { + nextChar(); + nextChar(); + nextChar(); + return { + type: Token.Punctuator, + value: '<<=', + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + if (ch1 === '>' && ch2 === '>' && ch3 === '=') { + nextChar(); + nextChar(); + nextChar(); + return { + type: Token.Punctuator, + value: '>>=', + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + // 2-character punctuators: <= >= == != ++ -- << >> && || + // += -= *= %= &= |= ^= /= + + if (ch2 === '=') { + if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) { + nextChar(); + nextChar(); + return { + type: Token.Punctuator, + value: ch1 + ch2, + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + } + + if (ch1 === ch2 && ('+-<>&|'.indexOf(ch1) >= 0)) { + if ('+-<>&|'.indexOf(ch2) >= 0) { + nextChar(); + nextChar(); + return { + type: Token.Punctuator, + value: ch1 + ch2, + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + } + + // The remaining 1-character punctuators. + + if ('[]<>+-*%&|^!~?:=/'.indexOf(ch1) >= 0) { + return { + type: Token.Punctuator, + value: nextChar(), + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + } + + // 7.8.3 Numeric Literals + + function scanNumericLiteral() { + var number, start, ch; + + ch = source[index]; + if (!isDecimalDigit(ch) && (ch !== '.')) { + return; + } + + start = index; + number = ''; + if (ch !== '.') { + number = nextChar(); + ch = source[index]; + + // Hex number starts with '0x'. + // Octal number starts with '0'. + if (number === '0') { + if (ch === 'x' || ch === 'X') { + number += nextChar(); + while (index < length) { + ch = source[index]; + if (!isHexDigit(ch)) { + break; + } + number += nextChar(); + } + + if (number.length <= 2) { + // only 0x + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + + if (index < length) { + ch = source[index]; + if (isIdentifierStart(ch) || isDecimalDigit(ch)) { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + } + return { + type: Token.NumericLiteral, + value: parseInt(number, 16), + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } else if (isOctalDigit(ch)) { + number += nextChar(); + while (index < length) { + ch = source[index]; + if (!isOctalDigit(ch)) { + break; + } + number += nextChar(); + } + + if (index < length) { + ch = source[index]; + if (isIdentifierStart(ch) || isDecimalDigit(ch)) { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + } + return { + type: Token.NumericLiteral, + value: parseInt(number, 8), + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + // decimal number starts with '0' such as '09' is illegal. + if (isDecimalDigit(ch)) { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + } + + while (index < length) { + ch = source[index]; + if (!isDecimalDigit(ch)) { + break; + } + number += nextChar(); + } + } + + if (ch === '.') { + number += nextChar(); + while (index < length) { + ch = source[index]; + if (!isDecimalDigit(ch)) { + break; + } + number += nextChar(); + } + } + + if (ch === 'e' || ch === 'E') { + number += nextChar(); + ch = source[index]; + if (ch === '+' || ch === '-' || isDecimalDigit(ch)) { + number += nextChar(); + while (index < length) { + ch = source[index]; + if (!isDecimalDigit(ch)) { + break; + } + number += nextChar(); + } + } else { + ch = 'character ' + ch; + if (index >= length) { + ch = ''; + } + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + } + + if (index < length) { + ch = source[index]; + if (isIdentifierStart(ch) || isDecimalDigit(ch)) { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + } + + return { + type: Token.NumericLiteral, + value: parseFloat(number), + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + // 7.8.4 String Literals + + function scanStringLiteral() { + var str = '', quote, start, ch, code, unescaped, restore, octal = false; + + quote = source[index]; + if (quote !== '\'' && quote !== '"') { + return; + } + start = index; + nextChar(); + + while (index < length) { + ch = nextChar(); + + if (ch === quote) { + quote = ''; + break; + } else if (ch === '\\') { + ch = nextChar(); + if (!isLineTerminator(ch)) { + switch (ch) { + case 'n': + str += '\n'; + break; + case 'r': + str += '\r'; + break; + case 't': + str += '\t'; + break; + case 'u': + case 'x': + restore = index; + unescaped = scanHexEscape(ch); + if (unescaped) { + str += unescaped; + } else { + index = restore; + str += ch; + } + break; + case 'b': + str += '\b'; + break; + case 'f': + str += '\f'; + break; + case 'v': + str += '\v'; + break; + + default: + if (isOctalDigit(ch)) { + code = '01234567'.indexOf(ch); + + // \0 is not octal escape sequence + if (code !== 0) { + octal = true; + } + + if (index < length && isOctalDigit(source[index])) { + octal = true; + code = code * 8 + '01234567'.indexOf(nextChar()); + + // 3 digits are only allowed when string starts + // with 0, 1, 2, 3 + if ('0123'.indexOf(ch) >= 0 && + index < length && + isOctalDigit(source[index])) { + code = code * 8 + '01234567'.indexOf(nextChar()); + } + } + str += String.fromCharCode(code); + } else { + str += ch; + } + break; + } + } else { + lineNumber += 1; + if (ch === '\r' && source[index] === '\n') { + nextChar(); + } + } + } else if (isLineTerminator(ch)) { + break; + } else { + str += ch; + } + } + + if (quote !== '') { + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + + return { + type: Token.StringLiteral, + value: str, + lineNumber: lineNumber, + lineStart: lineStart, + range: [start, index] + }; + } + + function scanRegExp() { + var str = '', ch, start, pattern, flags, value, classMarker = false, restore; + + buffer = null; + skipComment(); + + start = index; + ch = source[index]; + if (ch !== '/') { + return; + } + str = nextChar(); + + while (index < length) { + ch = nextChar(); + str += ch; + if (classMarker) { + if (ch === ']') { + classMarker = false; + } + } else { + if (ch === '\\') { + str += nextChar(); + } + if (ch === '/') { + break; + } + if (ch === '[') { + classMarker = true; + } + if (isLineTerminator(ch)) { + throwError({}, Messages.UnterminatedRegExp); + } + } + } + + if (str.length === 1) { + throwError({}, Messages.UnterminatedRegExp); + } + + // Exclude leading and trailing slash. + pattern = str.substr(1, str.length - 2); + + flags = ''; + while (index < length) { + ch = source[index]; + if (!isIdentifierPart(ch)) { + break; + } + + nextChar(); + if (ch === '\\' && index < length) { + ch = source[index]; + if (ch === 'u') { + nextChar(); + restore = index; + ch = scanHexEscape('u'); + if (ch) { + flags += ch; + str += '\\u'; + for (; restore < index; restore += 1) { + str += source[restore]; + } + } else { + index = restore; + flags += 'u'; + str += '\\u'; + } + } else { + str += '\\'; + } + } else { + flags += ch; + str += ch; + } + } + + try { + value = new RegExp(pattern, flags); + } catch (e) { + throwError({}, Messages.InvalidRegExp); + } + + return { + literal: str, + value: value, + range: [start, index] + }; + } + + function isIdentifierName(token) { + return token.type === Token.Identifier || + token.type === Token.Keyword || + token.type === Token.BooleanLiteral || + token.type === Token.NullLiteral; + } + + function advance() { + var ch, token; + + skipComment(); + + if (index >= length) { + return { + type: Token.EOF, + lineNumber: lineNumber, + lineStart: lineStart, + range: [index, index] + }; + } + + token = scanPunctuator(); + if (typeof token !== 'undefined') { + return token; + } + + ch = source[index]; + + if (ch === '\'' || ch === '"') { + return scanStringLiteral(); + } + + if (ch === '.' || isDecimalDigit(ch)) { + return scanNumericLiteral(); + } + + token = scanIdentifier(); + if (typeof token !== 'undefined') { + return token; + } + + throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); + } + + function lex() { + var token; + + if (buffer) { + index = buffer.range[1]; + lineNumber = buffer.lineNumber; + lineStart = buffer.lineStart; + token = buffer; + buffer = null; + return token; + } + + buffer = null; + return advance(); + } + + function lookahead() { + var pos, line, start; + + if (buffer !== null) { + return buffer; + } + + pos = index; + line = lineNumber; + start = lineStart; + buffer = advance(); + index = pos; + lineNumber = line; + lineStart = start; + + return buffer; + } + + // Return true if there is a line terminator before the next token. + + function peekLineTerminator() { + var pos, line, start, found; + + pos = index; + line = lineNumber; + start = lineStart; + skipComment(); + found = lineNumber !== line; + index = pos; + lineNumber = line; + lineStart = start; + + return found; + } + + // Throw an exception + + function throwError(token, messageFormat) { + var error, + args = Array.prototype.slice.call(arguments, 2), + msg = messageFormat.replace( + /%(\d)/g, + function (whole, index) { + return args[index] || ''; + } + ); + + if (typeof token.lineNumber === 'number') { + error = new Error('Line ' + token.lineNumber + ': ' + msg); + error.index = token.range[0]; + error.lineNumber = token.lineNumber; + error.column = token.range[0] - lineStart + 1; + } else { + error = new Error('Line ' + lineNumber + ': ' + msg); + error.index = index; + error.lineNumber = lineNumber; + error.column = index - lineStart + 1; + } + + throw error; + } + + // Throw an exception because of the token. + + function throwUnexpected(token) { + var s; + + if (token.type === Token.EOF) { + throwError(token, Messages.UnexpectedEOS); + } + + if (token.type === Token.NumericLiteral) { + throwError(token, Messages.UnexpectedNumber); + } + + if (token.type === Token.StringLiteral) { + throwError(token, Messages.UnexpectedString); + } + + if (token.type === Token.Identifier) { + throwError(token, Messages.UnexpectedIdentifier); + } + + if (token.type === Token.Keyword && isFutureReservedWord(token.value)) { + throwError(token, Messages.UnexpectedReserved); + } + + s = token.value; + if (s.length > 10) { + s = s.substr(0, 10) + '...'; + } + throwError(token, Messages.UnexpectedToken, s); + } + + // Expect the next token to match the specified punctuator. + // If not, an exception will be thrown. + + function expect(value) { + var token = lex(); + if (token.type !== Token.Punctuator || token.value !== value) { + throwUnexpected(token); + } + } + + // Expect the next token to match the specified keyword. + // If not, an exception will be thrown. + + function expectKeyword(keyword) { + var token = lex(); + if (token.type !== Token.Keyword || token.value !== keyword) { + throwUnexpected(token); + } + } + + // Return true if the next token matches the specified punctuator. + + function match(value) { + var token = lookahead(); + return token.type === Token.Punctuator && token.value === value; + } + + // Return true if the next token matches the specified keyword + + function matchKeyword(keyword) { + var token = lookahead(); + return token.type === Token.Keyword && token.value === keyword; + } + + // Return true if the next token is an assignment operator + + function matchAssign() { + var token = lookahead(), + op = token.value; + + if (token.type !== Token.Punctuator) { + return false; + } + return op === '=' || + op === '*=' || + op === '/=' || + op === '%=' || + op === '+=' || + op === '-=' || + op === '<<=' || + op === '>>=' || + op === '>>>=' || + op === '&=' || + op === '^=' || + op === '|='; + } + + + // Return true if expr is left hand side expression + + function isLeftHandSide(expr) { + return expr.type === Syntax.Identifier || + expr.type === Syntax.MemberExpression || + expr.type === Syntax.CallExpression || + expr.type === Syntax.NewExpression; + } + + + function consumeSemicolon() { + var token, line; + + // Catch the very common case first. + if (source[index] === ';') { + lex(); + return; + } + + line = lineNumber; + skipComment(); + if (lineNumber !== line) { + return; + } + + if (match(';')) { + lex(); + return; + } + + token = lookahead(); + if (token.type !== Token.EOF && !match('}')) { + throwUnexpected(token); + } + return; + } + + // 11.1.4 Array Initialiser + + function parseArrayInitialiser() { + var elements = [], + undef; + + expect('['); + + while (index < length) { + if (match(']')) { + lex(); + break; + } + + if (match(',')) { + lex(); + elements.push(undef); + } else { + elements.push(parseAssignmentExpression()); + + if (match(']')) { + lex(); + break; + } + + expect(','); + } + } + + return { + type: Syntax.ArrayExpression, + elements: elements + }; + } + + // 11.1.5 Object Initialiser + + function parsePropertyFunction(param) { + return { + type: Syntax.FunctionExpression, + id: null, + params: param, + body: parseBlock() + }; + } + + function parseObjectPropertyKey() { + var token = lex(), + key; + + switch (token.type) { + + case Token.StringLiteral: + case Token.NumericLiteral: + key = createLiteral(token); + break; + + case Token.Identifier: + case Token.Keyword: + case Token.BooleanLiteral: + case Token.NullLiteral: + key = { + type: Syntax.Identifier, + name: token.value + }; + break; + + default: + throwUnexpected(token); + } + + return key; + } + + function parseObjectProperty() { + var token, property, key, id, param; + + token = lookahead(); + + switch (token.type) { + + case Token.Identifier: + id = parseObjectPropertyKey(); + + // Property Assignment: Getter and Setter. + + if (token.value === 'get' && !match(':')) { + key = parseObjectPropertyKey(); + expect('('); + expect(')'); + property = { + type: Syntax.Property, + key: key, + value: parsePropertyFunction([]), + kind: 'get' + }; + } else if (token.value === 'set' && !match(':')) { + key = parseObjectPropertyKey(); + expect('('); + token = lookahead(); + if (token.type !== Token.Identifier) { + throwUnexpected(lex()); + } + param = [ parseObjectPropertyKey() ]; + expect(')'); + property = { + type: Syntax.Property, + key: key, + value: parsePropertyFunction(param), + kind: 'set' + }; + } else { + expect(':'); + property = { + type: Syntax.Property, + key: id, + value: parseAssignmentExpression(), + kind: 'init' + }; + } + break; + + case Token.Keyword: + case Token.BooleanLiteral: + case Token.NullLiteral: + case Token.StringLiteral: + case Token.NumericLiteral: + key = parseObjectPropertyKey(); + expect(':'); + property = { + type: Syntax.Property, + key: key, + value: parseAssignmentExpression(), + kind: 'init' + }; + break; + + default: + throwUnexpected(token); + } + + return property; + } + + function parseObjectInitialiser() { + var token, properties = [], property, name, kind, map = {}, toString = String; + + expect('{'); + + while (index < length) { + + token = lookahead(); + if (token.type === Token.Punctuator && token.value === '}') { + lex(); + break; + } + + property = parseObjectProperty(); + if (strict) { + if (property.key.type === Syntax.Identifier) { + name = property.key.name; + } else { + name = toString(property.key.value); + } + kind = (property.kind === 'init') ? PropertyKind.Data : (property.kind === 'get') ? PropertyKind.Get : PropertyKind.Set; + if (map.hasOwnProperty(name)) { + if (map[name] === PropertyKind.Data) { + if (kind === PropertyKind.Data) { + throwError({}, Messages.StrictDuplicateProperty); + } else { + throwError({}, Messages.StrictAccessorDataProperty); + } + } else { + if (kind === PropertyKind.Data) { + throwError({}, Messages.StrictAccessorDataProperty); + } else if (map[name] & kind) { + throwError({}, Messages.StrictAccessorGetSet); + } + } + map[name] |= kind; + } else { + map[name] = kind; + } + } + properties.push(property); + + token = lookahead(); + if (token.type === Token.Punctuator && token.value === '}') { + lex(); + break; + } + expect(','); + } + + return { + type: Syntax.ObjectExpression, + properties: properties + }; + } + + // 11.1 Primary Expressions + + function parsePrimaryExpression() { + var expr, + token = lookahead(), + type = token.type; + + if (type === Token.Identifier) { + return { + type: Syntax.Identifier, + name: lex().value + }; + } + + if (type === Token.StringLiteral || type === Token.NumericLiteral) { + return createLiteral(lex()); + } + + if (type === Token.Keyword) { + if (matchKeyword('this')) { + lex(); + return { + type: Syntax.ThisExpression + }; + } + + if (matchKeyword('function')) { + return parseFunctionExpression(); + } + } + + if (type === Token.BooleanLiteral) { + lex(); + token.value = (token.value === 'true'); + return createLiteral(token); + } + + if (type === Token.NullLiteral) { + return createLiteral(lex()); + } + + if (match('[')) { + return parseArrayInitialiser(); + } + + if (match('{')) { + return parseObjectInitialiser(); + } + + if (match('(')) { + lex(); + expr = parseExpression(); + expect(')'); + return expr; + } + + if (match('/') || match('/=')) { + return createLiteral(scanRegExp()); + } + + return throwUnexpected(lex()); + } + + // 11.2 Left-Hand-Side Expressions + + function parseArguments() { + var args = []; + + expect('('); + + if (!match(')')) { + while (index < length) { + args.push(parseAssignmentExpression()); + if (match(')')) { + break; + } + expect(','); + } + } + + expect(')'); + + return args; + } + + function parseNonComputedProperty() { + var token = lex(); + + if (!isIdentifierName(token)) { + throwUnexpected(token); + } + + return { + type: Syntax.Identifier, + name: token.value + }; + } + + function parseNonComputedMember(object) { + return { + type: Syntax.MemberExpression, + computed: false, + object: object, + property: parseNonComputedProperty() + }; + } + + function parseComputedMember(object) { + var property, expr; + + expect('['); + property = parseExpression(); + expr = { + type: Syntax.MemberExpression, + computed: true, + object: object, + property: property + }; + expect(']'); + return expr; + } + + function parseCallMember(object) { + return { + type: Syntax.CallExpression, + callee: object, + 'arguments': parseArguments() + }; + } + + function parseNewExpression() { + var expr; + + expectKeyword('new'); + + expr = { + type: Syntax.NewExpression, + callee: parseLeftHandSideExpression(), + 'arguments': [] + }; + + if (match('(')) { + expr['arguments'] = parseArguments(); + } + + return expr; + } + + function parseLeftHandSideExpressionAllowCall() { + var useNew, expr; + + useNew = matchKeyword('new'); + expr = useNew ? parseNewExpression() : parsePrimaryExpression(); + + while (index < length) { + if (match('.')) { + lex(); + expr = parseNonComputedMember(expr); + } else if (match('[')) { + expr = parseComputedMember(expr); + } else if (match('(')) { + expr = parseCallMember(expr); + } else { + break; + } + } + + return expr; + } + + function parseLeftHandSideExpression() { + var useNew, expr; + + useNew = matchKeyword('new'); + expr = useNew ? parseNewExpression() : parsePrimaryExpression(); + + while (index < length) { + if (match('.')) { + lex(); + expr = parseNonComputedMember(expr); + } else if (match('[')) { + expr = parseComputedMember(expr); + } else { + break; + } + } + + return expr; + } + + // 11.3 Postfix Expressions + + function parsePostfixExpression() { + var expr = parseLeftHandSideExpressionAllowCall(); + + if ((match('++') || match('--')) && !peekLineTerminator()) { + if (!isLeftHandSide(expr)) { + throwError(lookahead(), Messages.InvalidLHSInPostfixOp); + } + expr = { + type: Syntax.UpdateExpression, + operator: lex().value, + argument: expr, + prefix: false + }; + } + + return expr; + } + + // 11.4 Unary Operators + + function parseUnaryExpression() { + var token, expr; + + if (match('++') || match('--')) { + token = lex(); + expr = parseUnaryExpression(); + if (!isLeftHandSide(expr)) { + throwError(token.value, Messages.InvalidLHSInPrefixOp); + } + expr = { + type: Syntax.UpdateExpression, + operator: token.value, + argument: expr, + prefix: true + }; + return expr; + } + + if (match('+') || match('-') || match('~') || match('!')) { + expr = { + type: Syntax.UnaryExpression, + operator: lex().value, + argument: parseUnaryExpression() + }; + return expr; + } + + if (matchKeyword('delete') || matchKeyword('void') || matchKeyword('typeof')) { + expr = { + type: Syntax.UnaryExpression, + operator: lex().value, + argument: parseUnaryExpression() + }; + if (strict && expr.operator === 'delete' && expr.argument.type === 'Identifier') { + throwError({}, Messages.StrictDelete); + } + return expr; + } + + return parsePostfixExpression(); + } + + // 11.5 Multiplicative Operators + + function parseMultiplicativeExpression() { + var expr = parseUnaryExpression(); + + while (match('*') || match('/') || match('%')) { + expr = { + type: Syntax.BinaryExpression, + operator: lex().value, + left: expr, + right: parseUnaryExpression() + }; + } + + return expr; + } + + // 11.6 Additive Operators + + function parseAdditiveExpression() { + var expr = parseMultiplicativeExpression(); + + while (match('+') || match('-')) { + expr = { + type: Syntax.BinaryExpression, + operator: lex().value, + left: expr, + right: parseMultiplicativeExpression() + }; + } + + return expr; + } + + // 11.7 Bitwise Shift Operators + + function parseShiftExpression() { + var expr = parseAdditiveExpression(); + + while (match('<<') || match('>>') || match('>>>')) { + expr = { + type: Syntax.BinaryExpression, + operator: lex().value, + left: expr, + right: parseAdditiveExpression() + }; + } + + return expr; + } + // 11.8 Relational Operators + + function parseRelationalExpression() { + var expr, previousAllowIn; + + previousAllowIn = allowIn; + allowIn = true; + expr = parseShiftExpression(); + allowIn = previousAllowIn; + + if (match('<') || match('>') || match('<=') || match('>=')) { + expr = { + type: Syntax.BinaryExpression, + operator: lex().value, + left: expr, + right: parseRelationalExpression() + }; + } else if (allowIn && matchKeyword('in')) { + lex(); + expr = { + type: Syntax.BinaryExpression, + operator: 'in', + left: expr, + right: parseRelationalExpression() + }; + } else if (matchKeyword('instanceof')) { + lex(); + expr = { + type: Syntax.BinaryExpression, + operator: 'instanceof', + left: expr, + right: parseRelationalExpression() + }; + } + + return expr; + } + + // 11.9 Equality Operators + + function parseEqualityExpression() { + var expr = parseRelationalExpression(); + + while (match('==') || match('!=') || match('===') || match('!==')) { + expr = { + type: Syntax.BinaryExpression, + operator: lex().value, + left: expr, + right: parseRelationalExpression() + }; + } + + return expr; + } + + // 11.10 Binary Bitwise Operators + + function parseBitwiseANDExpression() { + var expr = parseEqualityExpression(); + + while (match('&')) { + lex(); + expr = { + type: Syntax.BinaryExpression, + operator: '&', + left: expr, + right: parseEqualityExpression() + }; + } + + return expr; + } + + function parseBitwiseORExpression() { + var expr = parseBitwiseANDExpression(); + + while (match('|')) { + lex(); + expr = { + type: Syntax.BinaryExpression, + operator: '|', + left: expr, + right: parseBitwiseANDExpression() + }; + } + + return expr; + } + + function parseBitwiseXORExpression() { + var expr = parseBitwiseORExpression(); + + while (match('^')) { + lex(); + expr = { + type: Syntax.BinaryExpression, + operator: '^', + left: expr, + right: parseBitwiseORExpression() + }; + } + + return expr; + } + + // 11.11 Binary Logical Operators + + function parseLogicalANDExpression() { + var expr = parseBitwiseXORExpression(); + + while (match('&&')) { + lex(); + expr = { + type: Syntax.LogicalExpression, + operator: '&&', + left: expr, + right: parseBitwiseXORExpression() + }; + } + + return expr; + } + + function parseLogicalORExpression() { + var expr = parseLogicalANDExpression(); + + while (match('||')) { + lex(); + expr = { + type: Syntax.LogicalExpression, + operator: '||', + left: expr, + right: parseLogicalANDExpression() + }; + } + + return expr; + } + + // 11.12 Conditional Operator + + function parseConditionalExpression() { + var expr, previousAllowIn, consequent; + + expr = parseLogicalORExpression(); + + if (match('?')) { + lex(); + previousAllowIn = allowIn; + allowIn = true; + consequent = parseAssignmentExpression(); + allowIn = previousAllowIn; + expect(':'); + + expr = { + type: Syntax.ConditionalExpression, + test: expr, + consequent: consequent, + alternate: parseAssignmentExpression() + }; + } + + return expr; + } + + // 11.13 Assignment Operators + + function parseAssignmentExpression() { + var expr; + + expr = parseConditionalExpression(); + + if (matchAssign()) { + if (!isLeftHandSide(expr)) { + throwError({}, Messages.InvalidLHSInAssignment); + } + expr = { + type: Syntax.AssignmentExpression, + operator: lex().value, + left: expr, + right: parseAssignmentExpression() + }; + } + + return expr; + } + + // 11.14 Comma Operator + + function parseExpression() { + var expr = parseAssignmentExpression(); + + if (match(',')) { + expr = { + type: Syntax.SequenceExpression, + expressions: [ expr ] + }; + + while (index < length) { + if (!match(',')) { + break; + } + lex(); + expr.expressions.push(parseAssignmentExpression()); + } + + } + return expr; + } + + // 12.1 Block + + function parseStatementList() { + var list = [], + statement; + + while (index < length) { + if (match('}')) { + break; + } + statement = parseSourceElement(); + if (typeof statement === 'undefined') { + break; + } + list.push(statement); + } + + return list; + } + + function parseBlock() { + var block; + + expect('{'); + + block = parseStatementList(); + + expect('}'); + + return { + type: Syntax.BlockStatement, + body: block + }; + } + + // 12.2 Variable Statement + + function parseVariableIdentifier() { + var token = lex(); + + if (token.type !== Token.Identifier) { + throwUnexpected(token); + } + + return { + type: Syntax.Identifier, + name: token.value + }; + } + + function parseVariableDeclaration(kind) { + var id = parseVariableIdentifier(), + init = null; + + if (kind === 'const') { + expect('='); + init = parseAssignmentExpression(); + } else if (match('=')) { + lex(); + init = parseAssignmentExpression(); + } + + return { + type: Syntax.VariableDeclarator, + id: id, + init: init + }; + } + + function parseVariableDeclarationList(kind) { + var list = []; + + while (index < length) { + list.push(parseVariableDeclaration(kind)); + if (!match(',')) { + break; + } + lex(); + } + + return list; + } + + function parseVariableStatement() { + var declarations; + + expectKeyword('var'); + + declarations = parseVariableDeclarationList(); + + consumeSemicolon(); + + return { + type: Syntax.VariableDeclaration, + declarations: declarations, + kind: 'var' + }; + } + + // kind may be `const` or `let` + // Both are experimental and not in the specification yet. + // see http://wiki.ecmascript.org/doku.php?id=harmony:const + // and http://wiki.ecmascript.org/doku.php?id=harmony:let + function parseConstLetDeclaration(kind) { + var declarations; + + expectKeyword(kind); + + declarations = parseVariableDeclarationList(kind); + + consumeSemicolon(); + + return { + type: Syntax.VariableDeclaration, + declarations: declarations, + kind: kind + }; + } + + // 12.3 Empty Statement + + function parseEmptyStatement() { + expect(';'); + + return { + type: Syntax.EmptyStatement + }; + } + + // 12.4 Expression Statement + + function parseExpressionStatement() { + var expr = parseExpression(); + + consumeSemicolon(); + + return { + type: Syntax.ExpressionStatement, + expression: expr + }; + } + + // 12.5 If statement + + function parseIfStatement() { + var test, consequent, alternate; + + expectKeyword('if'); + + expect('('); + + test = parseExpression(); + + expect(')'); + + consequent = parseStatement(); + + if (matchKeyword('else')) { + lex(); + alternate = parseStatement(); + } else { + alternate = null; + } + + return { + type: Syntax.IfStatement, + test: test, + consequent: consequent, + alternate: alternate + }; + } + + // 12.6 Iteration Statements + + function parseDoWhileStatement() { + var body, test; + + expectKeyword('do'); + + body = parseStatement(); + + expectKeyword('while'); + + expect('('); + + test = parseExpression(); + + expect(')'); + + if (match(';')) { + lex(); + } + + return { + type: Syntax.DoWhileStatement, + body: body, + test: test + }; + } + + function parseWhileStatement() { + var test, body; + + expectKeyword('while'); + + expect('('); + + test = parseExpression(); + + expect(')'); + + body = parseStatement(); + + return { + type: Syntax.WhileStatement, + test: test, + body: body + }; + } + + function parseForVariableDeclaration() { + var token = lex(); + + return { + type: Syntax.VariableDeclaration, + declarations: parseVariableDeclarationList(), + kind: token.value + }; + } + + function parseForStatement() { + var init, test, update, left, right, body; + + init = test = update = null; + + expectKeyword('for'); + + expect('('); + + if (match(';')) { + lex(); + } else { + if (matchKeyword('var') || matchKeyword('let')) { + allowIn = false; + init = parseForVariableDeclaration(); + allowIn = true; + + if (init.declarations.length === 1 && matchKeyword('in')) { + lex(); + left = init; + right = parseExpression(); + init = null; + } + } else { + allowIn = false; + init = parseExpression(); + allowIn = true; + + if (matchKeyword('in')) { + lex(); + left = init; + right = parseExpression(); + init = null; + if (!isLeftHandSide(left)) { + throwError({}, Messages.InvalidLHSInForIn); + } + } + } + + if (typeof left === 'undefined') { + expect(';'); + } + } + + if (typeof left === 'undefined') { + + if (!match(';')) { + test = parseExpression(); + } + expect(';'); + + if (!match(')')) { + update = parseExpression(); + } + } + + expect(')'); + + body = parseStatement(); + + if (typeof left === 'undefined') { + return { + type: Syntax.ForStatement, + init: init, + test: test, + update: update, + body: body + }; + } + + return { + type: Syntax.ForInStatement, + left: left, + right: right, + body: body, + each: false + }; + } + + // 12.7 The continue statement + + function parseContinueStatement() { + var token, label = null; + + expectKeyword('continue'); + + // Optimize the most common form: 'continue;'. + if (source[index] === ';') { + lex(); + return { + type: Syntax.ContinueStatement, + label: null + }; + } + + if (peekLineTerminator()) { + return { + type: Syntax.ContinueStatement, + label: null + }; + } + + token = lookahead(); + if (token.type === Token.Identifier) { + label = parseVariableIdentifier(); + } + + consumeSemicolon(); + + return { + type: Syntax.ContinueStatement, + label: label + }; + } + + // 12.8 The break statement + + function parseBreakStatement() { + var token, label = null; + + expectKeyword('break'); + + // Optimize the most common form: 'break;'. + if (source[index] === ';') { + lex(); + return { + type: Syntax.BreakStatement, + label: null + }; + } + + if (peekLineTerminator()) { + return { + type: Syntax.BreakStatement, + label: null + }; + } + + token = lookahead(); + if (token.type === Token.Identifier) { + label = parseVariableIdentifier(); + } + + consumeSemicolon(); + + return { + type: Syntax.BreakStatement, + label: label + }; + } + + // 12.9 The return statement + + function parseReturnStatement() { + var token, argument = null; + + expectKeyword('return'); + + // 'return' followed by a space and an identifier is very common. + if (source[index] === ' ') { + if (isIdentifierStart(source[index + 1])) { + argument = parseExpression(); + consumeSemicolon(); + return { + type: Syntax.ReturnStatement, + argument: argument + }; + } + } + + if (peekLineTerminator()) { + return { + type: Syntax.ReturnStatement, + argument: null + }; + } + + if (!match(';')) { + token = lookahead(); + if (!match('}') && token.type !== Token.EOF) { + argument = parseExpression(); + } + } + + consumeSemicolon(); + + return { + type: Syntax.ReturnStatement, + argument: argument + }; + } + + // 12.10 The with statement + + function parseWithStatement() { + var object, body; + + expectKeyword('with'); + + expect('('); + + object = parseExpression(); + + expect(')'); + + body = parseStatement(); + + return { + type: Syntax.WithStatement, + object: object, + body: body + }; + } + + // 12.10 The swith statement + + function parseSwitchCase(test) { + var consequent = [], + statement; + + while (index < length) { + if (match('}') || matchKeyword('default') || matchKeyword('case')) { + break; + } + statement = parseStatement(); + if (typeof statement === 'undefined') { + break; + } + consequent.push(statement); + } + + return { + type: Syntax.SwitchCase, + test: test, + consequent: consequent + }; + } + + function parseSwitchStatement() { + var discriminant, cases, test, consequent, statement; + + expectKeyword('switch'); + + expect('('); + + discriminant = parseExpression(); + + expect(')'); + + expect('{'); + + if (match('}')) { + lex(); + return { + type: Syntax.SwitchStatement, + discriminant: discriminant + }; + } + + cases = []; + + while (index < length) { + if (match('}')) { + break; + } + + if (matchKeyword('default')) { + lex(); + test = null; + } else { + expectKeyword('case'); + test = parseExpression(); + } + expect(':'); + + cases.push(parseSwitchCase(test)); + } + + expect('}'); + + return { + type: Syntax.SwitchStatement, + discriminant: discriminant, + cases: cases + }; + } + + // 12.13 The throw statement + + function parseThrowStatement() { + var argument; + + expectKeyword('throw'); + + if (peekLineTerminator()) { + throwError({}, Messages.NewlineAfterThrow); + } + + argument = parseExpression(); + + consumeSemicolon(); + + return { + type: Syntax.ThrowStatement, + argument: argument + }; + } + + // 12.14 The try statement + + function parseCatchClause() { + var param; + + expectKeyword('catch'); + + expect('('); + if (!match(')')) { + param = parseExpression(); + } + expect(')'); + + return { + type: Syntax.CatchClause, + param: param, + guard: null, + body: parseBlock() + }; + } + + function parseTryStatement() { + var block, handlers = [], param, finalizer = null; + + expectKeyword('try'); + + block = parseBlock(); + + if (matchKeyword('catch')) { + handlers.push(parseCatchClause()); + } + + if (matchKeyword('finally')) { + lex(); + finalizer = parseBlock(); + } + + if (handlers.length === 0 && !finalizer) { + throwError({}, Messages.NoCatchOrFinally); + } + + return { + type: Syntax.TryStatement, + block: block, + handlers: handlers, + finalizer: finalizer + }; + } + + // 12.15 The debugger statement + + function parseDebuggerStatement() { + expectKeyword('debugger'); + + consumeSemicolon(); + + return { + type: Syntax.DebuggerStatement + }; + } + + // 12 Statements + + function parseStatement() { + var token = lookahead(), + expr; + + if (token.type === Token.EOF) { + return; + } + + if (token.type === Token.Punctuator) { + switch (token.value) { + case ';': + return parseEmptyStatement(); + case '{': + return parseBlock(); + case '(': + return parseExpressionStatement(); + default: + break; + } + } + + if (token.type === Token.Keyword) { + switch (token.value) { + case 'break': + return parseBreakStatement(); + case 'continue': + return parseContinueStatement(); + case 'debugger': + return parseDebuggerStatement(); + case 'do': + return parseDoWhileStatement(); + case 'for': + return parseForStatement(); + case 'function': + return parseFunctionDeclaration(); + case 'if': + return parseIfStatement(); + case 'return': + return parseReturnStatement(); + case 'switch': + return parseSwitchStatement(); + case 'throw': + return parseThrowStatement(); + case 'try': + return parseTryStatement(); + case 'var': + return parseVariableStatement(); + case 'while': + return parseWhileStatement(); + case 'with': + return parseWithStatement(); + default: + break; + } + } + + expr = parseExpression(); + + // 12.12 Labelled Statements + if ((expr.type === Syntax.Identifier) && match(':')) { + lex(); + return { + type: Syntax.LabeledStatement, + label: expr, + body: parseStatement() + }; + } + + consumeSemicolon(); + + return { + type: Syntax.ExpressionStatement, + expression: expr + }; + } + + // 13 Function Definition + + function parseFunctionSourceElements() { + var sourceElement, sourceElements = [], token, directive; + + expect('{'); + + while (index < length) { + token = lookahead(); + if (token.type !== Token.StringLiteral) { + break; + } + + sourceElement = parseSourceElement(); + sourceElements.push(sourceElement); + if (sourceElement.expression.type !== Syntax.Literal) { + // this is not directive + break; + } + directive = sliceSource(token.range[0] + 1, token.range[1] - 1); + if (directive === 'use strict') { + strict = true; + } + } + + while (index < length) { + if (match('}')) { + break; + } + sourceElement = parseSourceElement(); + if (typeof sourceElement === 'undefined') { + break; + } + sourceElements.push(sourceElement); + } + + expect('}'); + + return { + type: Syntax.BlockStatement, + body: sourceElements + }; + } + + function parseFunctionDeclaration() { + var id, params = [], body, previousStrict; + + expectKeyword('function'); + id = parseVariableIdentifier(); + + expect('('); + + if (!match(')')) { + while (index < length) { + params.push(parseVariableIdentifier()); + if (match(')')) { + break; + } + expect(','); + } + } + + expect(')'); + + previousStrict = strict; + strict = false; + body = parseFunctionSourceElements(); + strict = previousStrict; + + return { + type: Syntax.FunctionDeclaration, + id: id, + params: params, + body: body + }; + } + + function parseFunctionExpression() { + var token, id = null, params = [], body, previousStrict; + + expectKeyword('function'); + + if (!match('(')) { + id = parseVariableIdentifier(); + } + + expect('('); + + if (!match(')')) { + while (index < length) { + params.push(parseVariableIdentifier()); + if (match(')')) { + break; + } + expect(','); + } + } + + expect(')'); + + previousStrict = strict; + strict = false; + body = parseFunctionSourceElements(); + strict = previousStrict; + + return { + type: Syntax.FunctionExpression, + id: id, + params: params, + body: body + }; + } + + // 14 Program + + function parseSourceElement() { + var token; + + token = lookahead(); + if (token.type === Token.EOF) { + return; + } + + if (token.type === Token.Keyword) { + switch (token.value) { + case 'const': + case 'let': + return parseConstLetDeclaration(token.value); + case 'function': + return parseFunctionDeclaration(); + default: + break; + } + } + + return parseStatement(); + } + + function parseSourceElements() { + var sourceElement, sourceElements = [], token, directive; + + while (index < length) { + token = lookahead(); + if (token.type !== Token.StringLiteral) { + break; + } + + sourceElement = parseSourceElement(); + sourceElements.push(sourceElement); + if (sourceElement.expression.type !== Syntax.Literal) { + // this is not directive + break; + } + directive = sliceSource(token.range[0] + 1, token.range[1] - 1); + if (directive === 'use strict') { + strict = true; + } + } + + while (index < length) { + sourceElement = parseSourceElement(); + if (typeof sourceElement === 'undefined') { + break; + } + sourceElements.push(sourceElement); + } + return sourceElements; + } + + function parseProgram() { + var program, previousStrict; + previousStrict = strict; + strict = false; + program = { + type: Syntax.Program, + body: parseSourceElements() + }; + strict = previousStrict; + return program; + } + + // The following functions are needed only when the option to preserve + // the comments is active. + + function addComment(start, end, type, value) { + if (typeof start !== 'number') { + return; + } + + // Because the way the actual token is scanned, often the comments + // (if any) are skipped twice during the lexical analysis. + // Thus, we need to skip adding a comment if the comment array already + // handled it. + if (extra.comments.length > 0) { + if (extra.comments[extra.comments.length - 1].range[1] > start) { + return; + } + } + + extra.comments.push({ + range: [start, end], + type: type, + value: value + }); + } + + function scanComment() { + var comment, ch, start, blockComment, lineComment; + + comment = ''; + blockComment = false; + lineComment = false; + + while (index < length) { + ch = source[index]; + + if (lineComment) { + ch = nextChar(); + if (isLineTerminator(ch)) { + lineComment = false; + addComment(start, index - 1, 'Line', comment); + if (ch === '\r' && source[index] === '\n') { + nextChar(); + } + lineNumber += 1; + lineStart = index + 1; + comment = ''; + } else { + comment += ch; + } + } else if (blockComment) { + ch = nextChar(); + comment += ch; + if (ch === '*') { + ch = source[index]; + if (ch === '/') { + comment = comment.substr(0, comment.length - 1); + blockComment = false; + nextChar(); + addComment(start, index - 1, 'Block', comment); + comment = ''; + } + } else if (isLineTerminator(ch)) { + if (ch === '\r' && source[index] === '\n') { + nextChar(); + } + lineNumber += 1; + lineStart = index + 1; + } + } else if (ch === '/') { + ch = source[index + 1]; + if (ch === '/') { + start = index; + nextChar(); + nextChar(); + lineComment = true; + } else if (ch === '*') { + start = index; + nextChar(); + nextChar(); + blockComment = true; + } else { + break; + } + } else if (isWhiteSpace(ch)) { + nextChar(); + } else if (isLineTerminator(ch)) { + nextChar(); + if (ch === '\r' && source[index] === '\n') { + nextChar(); + } + lineNumber += 1; + lineStart = index + 1; + } else { + break; + } + } + + addComment(start, index, (blockComment) ? 'Block' : 'Line', comment); + } + + function tokenTypeAsString(type) { + switch (type) { + case Token.BooleanLiteral: return 'Boolean'; + case Token.Identifier: return 'Identifier'; + case Token.Keyword: return 'Keyword'; + case Token.NullLiteral: return 'Null'; + case Token.NumericLiteral: return 'Numeric'; + case Token.Punctuator: return 'Punctuator'; + case Token.StringLiteral: return 'String'; + default: + throw new Error('Unknown token type'); + } + } + + function collectToken() { + var token = extra.advance(), + range, + value; + + if (token.type !== Token.EOF) { + range = [token.range[0], token.range[1] - 1]; + value = sliceSource(token.range[0], token.range[1]); + extra.tokens.push({ + type: tokenTypeAsString(token.type), + value: value, + range: range + }); + } + + return token; + } + + function collectRegex() { + var pos, regex, token; + + skipComment(); + + pos = index; + regex = extra.scanRegExp(); + + // Pop the previous token, which is likely '/' or '/=' + if (extra.tokens.length > 0) { + token = extra.tokens[extra.tokens.length - 1]; + if (token.range[0] === pos && token.type === 'Punctuator') { + if (token.value === '/' || token.value === '/=') { + extra.tokens.pop(); + } + } + } + + extra.tokens.push({ + type: 'RegularExpression', + value: regex.literal, + range: [pos, index - 1] + }); + + return regex; + } + + function createLiteral(token) { + return { + type: Syntax.Literal, + value: token.value + }; + } + + function createRawLiteral(token) { + return { + type: Syntax.Literal, + value: token.value, + raw: sliceSource(token.range[0], token.range[1]) + }; + } + + function wrapTrackingFunction(range, loc) { + + return function (parseFunction) { + + function isBinary(node) { + return node.type === Syntax.LogicalExpression || + node.type === Syntax.BinaryExpression; + } + + function visit(node) { + if (range) { + if (isBinary(node.left) && (typeof node.left.range === 'undefined')) { + visit(node.left); + } + if (isBinary(node.right) && (typeof node.right.range === 'undefined')) { + visit(node.right); + } + + // Expression enclosed in brackets () already has the correct range. + if (typeof node.range === 'undefined') { + node.range = [node.left.range[0], node.right.range[1]]; + } + } + + if (loc) { + if (isBinary(node.left) && (typeof node.left.loc === 'undefined')) { + visit(node.left); + } + if (isBinary(node.right) && (typeof node.right.loc === 'undefined')) { + visit(node.right); + } + + if (typeof node.loc === 'undefined') { + node.loc = { + start: node.left.loc.start, + end: node.right.loc.end + }; + } + } + } + + return function () { + var node, rangeInfo, locInfo; + + skipComment(); + rangeInfo = [index, 0]; + locInfo = { + start: { + line: lineNumber, + column: index - lineStart + } + }; + + node = parseFunction.apply(null, arguments); + if (typeof node === 'undefined') { + return; + } + + if (range) { + rangeInfo[1] = index - 1; + node.range = rangeInfo; + } + + if (loc) { + locInfo.end = { + line: lineNumber, + column: index - lineStart + }; + node.loc = locInfo; + } + + if (isBinary(node)) { + visit(node); + } + + if (node.type === Syntax.MemberExpression) { + if (typeof node.object.range !== 'undefined') { + node.range[0] = node.object.range[0]; + } + if (typeof node.object.loc !== 'undefined') { + node.loc.start = node.object.loc.start; + } + } + return node; + }; + + }; + } + + function patch() { + + var wrapTracking; + + if (extra.comments) { + extra.skipComment = skipComment; + skipComment = scanComment; + } + + if (extra.raw) { + extra.createLiteral = createLiteral; + createLiteral = createRawLiteral; + } + + if (extra.range || extra.loc) { + + wrapTracking = wrapTrackingFunction(extra.range, extra.loc); + + extra.parseAdditiveExpression = parseAdditiveExpression; + extra.parseAssignmentExpression = parseAssignmentExpression; + extra.parseBitwiseANDExpression = parseBitwiseANDExpression; + extra.parseBitwiseORExpression = parseBitwiseORExpression; + extra.parseBitwiseXORExpression = parseBitwiseXORExpression; + extra.parseBlock = parseBlock; + extra.parseFunctionSourceElements = parseFunctionSourceElements; + extra.parseCallMember = parseCallMember; + extra.parseCatchClause = parseCatchClause; + extra.parseComputedMember = parseComputedMember; + extra.parseConditionalExpression = parseConditionalExpression; + extra.parseConstLetDeclaration = parseConstLetDeclaration; + extra.parseEqualityExpression = parseEqualityExpression; + extra.parseExpression = parseExpression; + extra.parseForVariableDeclaration = parseForVariableDeclaration; + extra.parseFunctionDeclaration = parseFunctionDeclaration; + extra.parseFunctionExpression = parseFunctionExpression; + extra.parseLogicalANDExpression = parseLogicalANDExpression; + extra.parseLogicalORExpression = parseLogicalORExpression; + extra.parseMultiplicativeExpression = parseMultiplicativeExpression; + extra.parseNewExpression = parseNewExpression; + extra.parseNonComputedMember = parseNonComputedMember; + extra.parseNonComputedProperty = parseNonComputedProperty; + extra.parseObjectProperty = parseObjectProperty; + extra.parseObjectPropertyKey = parseObjectPropertyKey; + extra.parsePostfixExpression = parsePostfixExpression; + extra.parsePrimaryExpression = parsePrimaryExpression; + extra.parseProgram = parseProgram; + extra.parsePropertyFunction = parsePropertyFunction; + extra.parseRelationalExpression = parseRelationalExpression; + extra.parseStatement = parseStatement; + extra.parseShiftExpression = parseShiftExpression; + extra.parseSwitchCase = parseSwitchCase; + extra.parseUnaryExpression = parseUnaryExpression; + extra.parseVariableDeclaration = parseVariableDeclaration; + extra.parseVariableIdentifier = parseVariableIdentifier; + + parseAdditiveExpression = wrapTracking(extra.parseAdditiveExpression); + parseAssignmentExpression = wrapTracking(extra.parseAssignmentExpression); + parseBitwiseANDExpression = wrapTracking(extra.parseBitwiseANDExpression); + parseBitwiseORExpression = wrapTracking(extra.parseBitwiseORExpression); + parseBitwiseXORExpression = wrapTracking(extra.parseBitwiseXORExpression); + parseBlock = wrapTracking(extra.parseBlock); + parseFunctionSourceElements = wrapTracking(extra.parseFunctionSourceElements); + parseCallMember = wrapTracking(extra.parseCallMember); + parseCatchClause = wrapTracking(extra.parseCatchClause); + parseComputedMember = wrapTracking(extra.parseComputedMember); + parseConditionalExpression = wrapTracking(extra.parseConditionalExpression); + parseConstLetDeclaration = wrapTracking(extra.parseConstLetDeclaration); + parseEqualityExpression = wrapTracking(extra.parseEqualityExpression); + parseExpression = wrapTracking(extra.parseExpression); + parseForVariableDeclaration = wrapTracking(extra.parseForVariableDeclaration); + parseFunctionDeclaration = wrapTracking(extra.parseFunctionDeclaration); + parseFunctionExpression = wrapTracking(extra.parseFunctionExpression); + parseLogicalANDExpression = wrapTracking(extra.parseLogicalANDExpression); + parseLogicalORExpression = wrapTracking(extra.parseLogicalORExpression); + parseMultiplicativeExpression = wrapTracking(extra.parseMultiplicativeExpression); + parseNewExpression = wrapTracking(extra.parseNewExpression); + parseNonComputedMember = wrapTracking(extra.parseNonComputedMember); + parseNonComputedProperty = wrapTracking(extra.parseNonComputedProperty); + parseObjectProperty = wrapTracking(extra.parseObjectProperty); + parseObjectPropertyKey = wrapTracking(extra.parseObjectPropertyKey); + parsePostfixExpression = wrapTracking(extra.parsePostfixExpression); + parsePrimaryExpression = wrapTracking(extra.parsePrimaryExpression); + parseProgram = wrapTracking(extra.parseProgram); + parsePropertyFunction = wrapTracking(extra.parsePropertyFunction); + parseRelationalExpression = wrapTracking(extra.parseRelationalExpression); + parseStatement = wrapTracking(extra.parseStatement); + parseShiftExpression = wrapTracking(extra.parseShiftExpression); + parseSwitchCase = wrapTracking(extra.parseSwitchCase); + parseUnaryExpression = wrapTracking(extra.parseUnaryExpression); + parseVariableDeclaration = wrapTracking(extra.parseVariableDeclaration); + parseVariableIdentifier = wrapTracking(extra.parseVariableIdentifier); + } + + if (typeof extra.tokens !== 'undefined') { + extra.advance = advance; + extra.scanRegExp = scanRegExp; + + advance = collectToken; + scanRegExp = collectRegex; + } + } + + function unpatch() { + if (typeof extra.skipComment === 'function') { + skipComment = extra.skipComment; + } + + if (extra.raw) { + createLiteral = extra.createLiteral; + } + + if (extra.range || extra.loc) { + parseAdditiveExpression = extra.parseAdditiveExpression; + parseAssignmentExpression = extra.parseAssignmentExpression; + parseBitwiseANDExpression = extra.parseBitwiseANDExpression; + parseBitwiseORExpression = extra.parseBitwiseORExpression; + parseBitwiseXORExpression = extra.parseBitwiseXORExpression; + parseBlock = extra.parseBlock; + parseFunctionSourceElements = extra.parseFunctionSourceElements; + parseCallMember = extra.parseCallMember; + parseCatchClause = extra.parseCatchClause; + parseComputedMember = extra.parseComputedMember; + parseConditionalExpression = extra.parseConditionalExpression; + parseConstLetDeclaration = extra.parseConstLetDeclaration; + parseEqualityExpression = extra.parseEqualityExpression; + parseExpression = extra.parseExpression; + parseForVariableDeclaration = extra.parseForVariableDeclaration; + parseFunctionDeclaration = extra.parseFunctionDeclaration; + parseFunctionExpression = extra.parseFunctionExpression; + parseLogicalANDExpression = extra.parseLogicalANDExpression; + parseLogicalORExpression = extra.parseLogicalORExpression; + parseMultiplicativeExpression = extra.parseMultiplicativeExpression; + parseNewExpression = extra.parseNewExpression; + parseNonComputedMember = extra.parseNonComputedMember; + parseNonComputedProperty = extra.parseNonComputedProperty; + parseObjectProperty = extra.parseObjectProperty; + parseObjectPropertyKey = extra.parseObjectPropertyKey; + parsePrimaryExpression = extra.parsePrimaryExpression; + parsePostfixExpression = extra.parsePostfixExpression; + parseProgram = extra.parseProgram; + parsePropertyFunction = extra.parsePropertyFunction; + parseRelationalExpression = extra.parseRelationalExpression; + parseStatement = extra.parseStatement; + parseShiftExpression = extra.parseShiftExpression; + parseSwitchCase = extra.parseSwitchCase; + parseUnaryExpression = extra.parseUnaryExpression; + parseVariableDeclaration = extra.parseVariableDeclaration; + parseVariableIdentifier = extra.parseVariableIdentifier; + } + + if (typeof extra.lex === 'function') { + lex = extra.lex; + } + + if (typeof extra.scanRegExp === 'function') { + advance = extra.advance; + scanRegExp = extra.scanRegExp; + } + } + + function stringToArray(str) { + var length = str.length, + result = [], + i; + for (i = 0; i < length; i += 1) { + result[i] = str.charAt(i); + } + return result; + } + + function parse(code, options) { + var program; + + source = code; + index = 0; + lineNumber = (source.length > 0) ? 1 : 0; + lineStart = 0; + length = source.length; + buffer = null; + allowIn = true; + + extra = {}; + if (typeof options !== 'undefined') { + extra.range = (typeof options.range === 'boolean') && options.range; + extra.loc = (typeof options.loc === 'boolean') && options.loc; + extra.raw = (typeof options.raw === 'boolean') && options.raw; + if (typeof options.tokens === 'boolean' && options.tokens) { + extra.tokens = []; + } + if (typeof options.comment === 'boolean' && options.comment) { + extra.comments = []; + } + } + + if (length > 0) { + if (typeof source[0] === 'undefined') { + // Try first to convert to a string. This is good as fast path + // for old IE which understands string indexing for string + // literals only and not for string object. + if (code instanceof String) { + source = code.valueOf(); + } + + // Force accessing the characters via an array. + if (typeof source[0] === 'undefined') { + source = stringToArray(code); + } + } + } + + patch(); + try { + program = parseProgram(); + if (typeof extra.comments !== 'undefined') { + program.comments = extra.comments; + } + if (typeof extra.tokens !== 'undefined') { + program.tokens = extra.tokens; + } + } catch (e) { + throw e; + } finally { + unpatch(); + extra = {}; + } + + return program; + } + + // Executes visitor on the object and its children (recursively). + + function traverse(object, visitor, master) { + var key, child, parent, path; + + parent = (typeof master === 'undefined') ? [] : master; + + if (visitor.call(null, object, parent) === false) { + return; + } + for (key in object) { + if (object.hasOwnProperty(key)) { + child = object[key]; + path = [ object ]; + path.push(parent); + if (typeof child === 'object' && child !== null) { + traverse(child, visitor, path); + } + } + } + } + + // Insert a prolog in the body of every function. + // It will be in the form of a function call: + // + // traceName(object); + // + // where the object contains the following properties: + // + // 'name' holds the name of the function + // 'lineNumber' holds the starting line number of the function block + // 'range' contains the index-based range of the function + // + // The name of the function represents the associated reference for + // the function (deduced on a best-effort basis if it is not + // a function declaration). + // + // If traceName is a function instead of a string, it will be invoked and + // the result will be used as the entire prolog. The arguments for the + // invocation are the function name, range, and location info. + + function traceFunctionEntrance(traceName) { + + return function (code) { + var tree, + functionList, + param, + signature, + pos, + i; + + + tree = parse(code, { range: true, loc: true }); + + functionList = []; + traverse(tree, function (node, path) { + var parent, name; + if (node.type === Syntax.FunctionDeclaration) { + functionList.push({ + name: node.id.name, + range: node.range, + loc: node.loc, + blockStart: node.body.range[0] + }); + } else if (node.type === Syntax.FunctionExpression) { + parent = path[0]; + if (parent.type === Syntax.AssignmentExpression) { + if (typeof parent.left.range !== 'undefined') { + functionList.push({ + name: code.slice(parent.left.range[0], + parent.left.range[1] + 1), + range: node.range, + loc: node.loc, + blockStart: node.body.range[0] + }); + } + } else if (parent.type === Syntax.VariableDeclarator) { + functionList.push({ + name: parent.id.name, + range: node.range, + loc: node.loc, + blockStart: node.body.range[0] + }); + } else if (parent.type === Syntax.CallExpression) { + functionList.push({ + name: parent.id ? parent.id.name : '[Anonymous]', + range: node.range, + loc: node.loc, + blockStart: node.body.range[0] + }); + } else if (typeof parent.length === 'number') { + functionList.push({ + name: parent.id ? parent.id.name : '[Anonymous]', + range: node.range, + loc: node.loc, + blockStart: node.body.range[0] + }); + } else if (typeof parent.key !== 'undefined') { + if (parent.key.type === 'Identifier') { + if (parent.value === node && parent.key.name) { + functionList.push({ + name: parent.key.name, + range: node.range, + loc: node.loc, + blockStart: node.body.range[0] + }); + } + } + } + } + }); + + // Insert the instrumentation code from the last entry. + // This is to ensure that the range for each entry remains valid) + // (it won't shift due to some new inserting string before the range). + for (i = functionList.length - 1; i >= 0; i -= 1) { + param = { + name: functionList[i].name, + range: functionList[i].range, + loc: functionList[i].loc + }; + if (typeof traceName === 'function') { + signature = traceName.call(null, param); + } else { + signature = traceName + '({ '; + signature += 'name: \'' + functionList[i].name + '\', '; + if (typeof functionList[i].loc !== 'undefined') { + signature += 'lineNumber: ' + functionList[i].loc.start.line + ', '; + } + signature += 'range: [' + functionList[i].range[0] + ', ' + + functionList[i].range[1] + '] '; + signature += '});'; + } + pos = functionList[i].blockStart + 1; + code = code.slice(0, pos) + '\n' + signature + code.slice(pos, code.length); + } + + return code; + }; + } + + function modify(code, modifiers) { + var i; + + if (Object.prototype.toString.call(modifiers) === '[object Array]') { + for (i = 0; i < modifiers.length; i += 1) { + code = modifiers[i].call(null, code); + } + } else if (typeof modifiers === 'function') { + code = modifiers.call(null, code); + } else { + throw new Error('Wrong use of esprima.modify() function'); + } + + return code; + } + + function unicodeEscape(ch) { + var result, i; + result = ch.charCodeAt(0).toString(16); + for (i = result.length; i < 4; i += 1) { + result = '0' + result; + } + return '\\u' + result; + } + + function escapeString(str) { + var result = '', i, len, ch; + + if (typeof str[0] === 'undefined') { + str = stringToArray(str); + } + + for (i = 0, len = str.length; i < len; i += 1) { + ch = str[i]; + if ('\'\\\b\f\n\r\t'.indexOf(ch) >= 0) { + result += '\\'; + switch (ch) { + case '\'': + result += '\''; + break; + case '\\': + result += '\\'; + break; + case '\b': + result += 'b'; + break; + case '\f': + result += 'f'; + break; + case '\n': + result += 'n'; + break; + case '\r': + result += 'r'; + break; + case '\t': + result += 't'; + break; + } + } else if (ch < ' ' || ch.charCodeAt(0) >= 0x80) { + result += unicodeEscape(ch); + } else { + result += ch; + } + } + + return '\'' + result + '\''; + } + + BinaryPrecedence = { + '||': Precedence.LogicalOR, + '&&': Precedence.LogicalAND, + '^': Precedence.LogicalXOR, + '|': Precedence.BitwiseOR, + '&': Precedence.BitwiseAND, + '==': Precedence.Equality, + '!=': Precedence.Equality, + '===': Precedence.Equality, + '!==': Precedence.Equality, + '<': Precedence.Relational, + '>': Precedence.Relational, + '<=': Precedence.Relational, + '>=': Precedence.Relational, + 'in': Precedence.Relational, + 'instanceof': Precedence.Relational, + '<<': Precedence.BitwiseSHIFT, + '>>': Precedence.BitwiseSHIFT, + '>>>': Precedence.BitwiseSHIFT, + '+': Precedence.Additive, + '-': Precedence.Additive, + '*': Precedence.Multiplicative, + '%': Precedence.Multiplicative, + '/': Precedence.Multiplicative + }; + + function addIndent(stmt) { + return base + stmt; + } + + function parenthesize(text, current, should) { + return (current < should) ? '(' + text + ')' : text; + } + + function maybeBlock(stmt, suffix) { + var previousBase, result; + + if (stmt.type === Syntax.BlockStatement) { + result = ' ' + generateStatement(stmt); + if (suffix) { + return result + ' '; + } + return result; + } + + if (stmt.type === Syntax.EmptyStatement) { + result = ';'; + } else { + previousBase = base; + base += indent; + result = '\n' + addIndent(generateStatement(stmt)); + base = previousBase; + } + + if (suffix) { + return result + addIndent('\n'); + } + return result; + } + + function generateFunctionBody(node) { + var result, i, len; + result = '('; + for (i = 0, len = node.params.length; i < len; i += 1) { + result += node.params[i].name; + if ((i + 1) < len) { + result += ', '; + } + } + return result + ')' + maybeBlock(node.body); + } + + function generateExpression(expr, precedence) { + var result, currentPrecedence, previousBase, i, len, raw; + + if (!precedence) { + precedence = Precedence.SequenceExpression; + } + + switch (expr.type) { + case Syntax.SequenceExpression: + result = ''; + for (i = 0, len = expr.expressions.length; i < len; i += 1) { + result += generateExpression(expr.expressions[i], Precedence.Assignment); + if ((i + 1) < len) { + result += ', '; + } + } + result = parenthesize(result, Precedence.Sequence, precedence); + break; + + case Syntax.AssignmentExpression: + result = parenthesize( + generateExpression(expr.left) + ' ' + expr.operator + ' ' + + generateExpression(expr.right, Precedence.Assignment), + Precedence.Assignment, + precedence + ); + break; + + case Syntax.ConditionalExpression: + result = parenthesize( + generateExpression(expr.test, Precedence.LogicalOR) + ' ? ' + + generateExpression(expr.consequent, Precedence.Assignment) + ' : ' + + generateExpression(expr.alternate, Precedence.Assignment), + Precedence.Conditional, + precedence + ); + break; + + case Syntax.LogicalExpression: + case Syntax.BinaryExpression: + currentPrecedence = BinaryPrecedence[expr.operator]; + + result = generateExpression(expr.left, currentPrecedence) + + ' ' + expr.operator + ' ' + + generateExpression(expr.right, currentPrecedence + 1); + if (expr.operator === 'in') { + // TODO parenthesize only in allowIn = false case + result = '(' + result + ')'; + } else { + result = parenthesize(result, currentPrecedence, precedence); + } + break; + + case Syntax.CallExpression: + result = ''; + for (i = 0, len = expr['arguments'].length; i < len; i += 1) { + result += generateExpression(expr['arguments'][i], Precedence.Assignment); + if ((i + 1) < len) { + result += ', '; + } + } + result = parenthesize( + generateExpression(expr.callee, Precedence.Call) + '(' + result + ')', + Precedence.Call, + precedence + ); + break; + + case Syntax.NewExpression: + result = ''; + for (i = 0, len = expr['arguments'].length; i < len; i += 1) { + result += generateExpression(expr['arguments'][i], Precedence.Assignment); + if ((i + 1) < len) { + result += ', '; + } + } + result = parenthesize( + 'new ' + generateExpression(expr.callee, Precedence.New) + '(' + result + ')', + Precedence.New, + precedence + ); + break; + + case Syntax.MemberExpression: + result = generateExpression(expr.object, Precedence.Call); + if (expr.computed) { + result += '[' + generateExpression(expr.property) + ']'; + } else { + if (expr.object.type === Syntax.Literal && typeof expr.object.value === 'number') { + if (result.indexOf('.') < 0) { + if (!/[eExX]/.test(result) && !(result.length >= 2 && result[0] === '0')) { + result += '.'; + } + } + } + result += '.' + expr.property.name; + } + result = parenthesize(result, Precedence.Member, precedence); + break; + + case Syntax.UnaryExpression: + result = expr.operator; + if (result.length > 2) { + result += ' '; + } + result = parenthesize( + result + generateExpression(expr.argument, Precedence.Unary), + Precedence.Unary, + precedence + ); + break; + + case Syntax.UpdateExpression: + if (expr.prefix) { + result = parenthesize( + expr.operator + + generateExpression(expr.argument, Precedence.Unary), + Precedence.Unary, + precedence + ); + } else { + result = parenthesize( + generateExpression(expr.argument, Precedence.Postfix) + + expr.operator, + Precedence.Postfix, + precedence + ); + } + break; + + case Syntax.FunctionExpression: + result = 'function '; + if (expr.id) { + result += expr.id.name; + } + result += generateFunctionBody(expr); + break; + + case Syntax.ArrayExpression: + if (!expr.elements.length) { + result = '[]'; + break; + } + result = '[\n'; + previousBase = base; + base += indent; + for (i = 0, len = expr.elements.length; i < len; i += 1) { + if (!expr.elements[i]) { + result += addIndent(''); + if ((i + 1) === len) { + result += ','; + } + } else { + result += addIndent(generateExpression(expr.elements[i], Precedence.Assignment)); + } + if ((i + 1) < len) { + result += ',\n'; + } + } + base = previousBase; + result += '\n' + addIndent(']'); + break; + + case Syntax.Property: + if (expr.kind === 'get' || expr.kind === 'set') { + result = expr.kind + ' ' + generateExpression(expr.key) + + generateFunctionBody(expr.value); + } else { + result = generateExpression(expr.key) + ': ' + + generateExpression(expr.value, Precedence.Assignment); + } + break; + + case Syntax.ObjectExpression: + if (!expr.properties.length) { + result = '{}'; + break; + } + result = '{\n'; + previousBase = base; + base += indent; + for (i = 0, len = expr.properties.length; i < len; i += 1) { + result += addIndent(generateExpression(expr.properties[i])); + if ((i + 1) < len) { + result += ',\n'; + } + } + base = previousBase; + result += '\n' + addIndent('}'); + break; + + case Syntax.ThisExpression: + result = 'this'; + break; + + case Syntax.Identifier: + result = expr.name; + break; + + case Syntax.Literal: + if (expr.hasOwnProperty('raw')) { + try { + raw = parse(expr.raw).body[0].expression; + if (raw.type === Syntax.Literal) { + if (raw.value === expr.value) { + result = expr.raw; + break; + } + } + } catch (e) { + // not use raw property + } + } + + if (expr.value === null) { + result = 'null'; + break; + } + + if (typeof expr.value === 'string') { + result = escapeString(expr.value); + break; + } + + if (typeof expr.value === 'number' && expr.value === Infinity) { + // Infinity is variable + result = '1e+1000'; + break; + } + + result = expr.value.toString(); + break; + + default: + break; + } + + if (!result) { + throw new Error('Unknown expression type: ' + expr.type); + } + return result; + } + + function generateStatement(stmt) { + var i, len, result, previousBase; + + switch (stmt.type) { + case Syntax.BlockStatement: + result = '{\n'; + + previousBase = base; + base += indent; + for (i = 0, len = stmt.body.length; i < len; i += 1) { + result += addIndent(generateStatement(stmt.body[i])) + '\n'; + } + base = previousBase; + + result += addIndent('}'); + break; + + case Syntax.BreakStatement: + if (stmt.label) { + result = 'break ' + stmt.label.name + ';'; + } else { + result = 'break;'; + } + break; + + case Syntax.ContinueStatement: + if (stmt.label) { + result = 'continue ' + stmt.label.name + ';'; + } else { + result = 'continue;'; + } + break; + + case Syntax.DoWhileStatement: + result = 'do' + maybeBlock(stmt.body, true) + 'while (' + generateExpression(stmt.test) + ');'; + break; + + case Syntax.CatchClause: + previousBase = base; + base += indent; + result = ' catch (' + generateExpression(stmt.param) + ')'; + base = previousBase; + result += maybeBlock(stmt.body); + break; + + case Syntax.DebuggerStatement: + result = 'debugger;'; + break; + + case Syntax.EmptyStatement: + result = ';'; + break; + + case Syntax.ExpressionStatement: + result = generateExpression(stmt.expression); + // 12.4 '{', 'function' is not allowed in this position. + // wrap espression with parentheses + if (result[0] === '{' || result.indexOf('function ') === 0) { + result = '(' + result + ');'; + } else { + result += ';'; + } + break; + + case Syntax.VariableDeclarator: + if (stmt.init) { + result = stmt.id.name + ' = ' + generateExpression(stmt.init, Precedence.AssignmentExpression); + } else { + result = stmt.id.name; + } + break; + + case Syntax.VariableDeclaration: + result = stmt.kind + ' '; + // special path for + // var x = function () { + // }; + if (stmt.declarations.length === 1 && stmt.declarations[0].init && + stmt.declarations[0].init.type === Syntax.FunctionExpression) { + result += generateStatement(stmt.declarations[0]); + } else { + previousBase = base; + base += indent; + for (i = 0, len = stmt.declarations.length; i < len; i += 1) { + result += generateStatement(stmt.declarations[i]); + if ((i + 1) < len) { + result += ', '; + } + } + base = previousBase; + } + result += ';'; + break; + + case Syntax.ThrowStatement: + result = 'throw ' + generateExpression(stmt.argument) + ';'; + break; + + case Syntax.TryStatement: + result = 'try' + maybeBlock(stmt.block); + for (i = 0, len = stmt.handlers.length; i < len; i += 1) { + result += generateStatement(stmt.handlers[i]); + } + if (stmt.finalizer) { + result += ' finally' + maybeBlock(stmt.finalizer); + } + break; + + case Syntax.SwitchStatement: + previousBase = base; + base += indent; + result = 'switch (' + generateExpression(stmt.discriminant) + ') {\n'; + base = previousBase; + if (stmt.cases) { + for (i = 0, len = stmt.cases.length; i < len; i += 1) { + result += addIndent(generateStatement(stmt.cases[i])) + '\n'; + } + } + result += addIndent('}'); + break; + + case Syntax.SwitchCase: + previousBase = base; + base += indent; + if (stmt.test) { + result = 'case ' + generateExpression(stmt.test) + ':'; + } else { + result = 'default:'; + } + + i = 0; + len = stmt.consequent.length; + if (len && stmt.consequent[0].type === Syntax.BlockStatement) { + result += maybeBlock(stmt.consequent[0]); + i = 1; + } + + for (; i < len; i += 1) { + result += '\n' + addIndent(generateStatement(stmt.consequent[i])); + } + + base = previousBase; + break; + + case Syntax.IfStatement: + if (stmt.alternate) { + if (stmt.alternate.type === Syntax.IfStatement) { + previousBase = base; + base += indent; + result = 'if (' + generateExpression(stmt.test) + ')'; + base = previousBase; + result += maybeBlock(stmt.consequent, true) + 'else ' + generateStatement(stmt.alternate); + } else { + previousBase = base; + base += indent; + result = 'if (' + generateExpression(stmt.test) + ')'; + base = previousBase; + result += maybeBlock(stmt.consequent, true) + 'else' + maybeBlock(stmt.alternate); + } + } else { + previousBase = base; + base += indent; + result = 'if (' + generateExpression(stmt.test) + ')'; + base = previousBase; + result += maybeBlock(stmt.consequent); + } + break; + + case Syntax.ForStatement: + previousBase = base; + base += indent; + result = 'for ('; + if (stmt.init) { + if (stmt.init.type === Syntax.VariableDeclaration) { + result += generateStatement(stmt.init); + } else { + result += generateExpression(stmt.init) + ';'; + } + } else { + result += ';'; + } + + if (stmt.test) { + result += ' ' + generateExpression(stmt.test) + ';'; + } else { + result += ';'; + } + + if (stmt.update) { + result += ' ' + generateExpression(stmt.update) + ')'; + } else { + result += ')'; + } + base = previousBase; + + result += maybeBlock(stmt.body); + break; + + case Syntax.ForInStatement: + result = 'for ('; + if (stmt.left.type === Syntax.VariableDeclaration) { + previousBase = base; + base += indent + indent; + result += stmt.left.kind + ' ' + generateStatement(stmt.left.declarations[0]); + base = previousBase; + } else { + previousBase = base; + base += indent; + result += generateExpression(stmt.left); + base = previousBase; + } + + previousBase = base; + base += indent; + result += ' in ' + generateExpression(stmt.right) + ')'; + base = previousBase; + result += maybeBlock(stmt.body); + break; + + case Syntax.LabeledStatement: + result = stmt.label.name + ':' + maybeBlock(stmt.body); + break; + + case Syntax.Program: + result = ''; + for (i = 0, len = stmt.body.length; i < len; i += 1) { + result += generateStatement(stmt.body[i]); + if ((i + 1) < len) { + result += '\n'; + } + } + break; + + case Syntax.FunctionDeclaration: + result = 'function '; + if (stmt.id) { + result += stmt.id.name; + } + result += generateFunctionBody(stmt); + break; + + case Syntax.ReturnStatement: + if (stmt.argument) { + result = 'return ' + generateExpression(stmt.argument) + ';'; + } else { + result = 'return;'; + } + break; + + case Syntax.WhileStatement: + previousBase = base; + base += indent; + result = 'while (' + generateExpression(stmt.test) + ')'; + base = previousBase; + result += maybeBlock(stmt.body); + break; + + case Syntax.WithStatement: + previousBase = base; + base += indent; + result = 'with (' + generateExpression(stmt.object) + ')'; + base = previousBase; + result += maybeBlock(stmt.body); + break; + + default: + break; + } + + if (!result) { + throw new Error('Unknown statement type: ' + stmt.type); + } + return result; + } + + function generate(node, options) { + if (typeof options !== 'undefined') { + base = options.base || ''; + indent = options.indent || ' '; + } else { + base = ''; + indent = ' '; + } + + switch (node.type) { + case Syntax.BlockStatement: + case Syntax.BreakStatement: + case Syntax.CatchClause: + case Syntax.ContinueStatement: + case Syntax.DoWhileStatement: + case Syntax.DebuggerStatement: + case Syntax.EmptyStatement: + case Syntax.ExpressionStatement: + case Syntax.ForStatement: + case Syntax.ForInStatement: + case Syntax.FunctionDeclaration: + case Syntax.IfStatement: + case Syntax.LabeledStatement: + case Syntax.Program: + case Syntax.ReturnStatement: + case Syntax.SwitchStatement: + case Syntax.SwitchCase: + case Syntax.ThrowStatement: + case Syntax.TryStatement: + case Syntax.VariableDeclaration: + case Syntax.VariableDeclarator: + case Syntax.WhileStatement: + case Syntax.WithStatement: + return generateStatement(node); + + case Syntax.AssignmentExpression: + case Syntax.ArrayExpression: + case Syntax.BinaryExpression: + case Syntax.CallExpression: + case Syntax.ConditionalExpression: + case Syntax.FunctionExpression: + case Syntax.Identifier: + case Syntax.Literal: + case Syntax.LogicalExpression: + case Syntax.MemberExpression: + case Syntax.NewExpression: + case Syntax.ObjectExpression: + case Syntax.Property: + case Syntax.SequenceExpression: + case Syntax.ThisExpression: + case Syntax.UnaryExpression: + case Syntax.UpdateExpression: + return generateExpression(node); + + default: + break; + } + throw new Error('Unknown node type: ' + node.type); + } + + // Sync with package.json. + exports.version = '0.9.8'; + + exports.parse = parse; + + exports.modify = modify; + + exports.generate = generate; + + exports.Tracer = { + FunctionEntrance: traceFunctionEntrance + }; + +}(typeof exports === 'undefined' ? (esprima = {}) : exports)); +/* vim: set sw=4 ts=4 et tw=80 : */ diff --git a/node_modules/esprima/index.html b/node_modules/esprima/index.html new file mode 100644 index 000000000..3f9b0c7c5 --- /dev/null +++ b/node_modules/esprima/index.html @@ -0,0 +1,92 @@ + + + + +Esprima + + + +
+ + + +

Esprima ECMAScript parsing infrastructure for multipurpose analysis

+ +
+

Esprima (esprima.org) is an educational +ECMAScript +(also popularly known as JavaScript) +parsing infrastructure for multipurpose analysis. It is also written in ECMAScript.

+ +

Esprima can be used in a web browser:

+ +
<script src="esprima.js"><script>
+ +

or in a Node.js application via the package manager:

+ +
npm install esprima
+ +

Esprima parser output is compatible with Mozilla (SpiderMonkey) +Parser API.

+ +

A very simple example:

+ +
esprima.parse('var answer=42').body[0].declarations[0].init
+ +

produces the following object:

+ +
{ type: 'Literal', value: 42 }
+ +

Esprima is still in the development, for now please check +the wiki documentation.

+ +

Since it is not comprehensive nor complete, refer to the +issue tracker for +known problems +and future plans. +Esprima is supported on many browsers: +IE 6+, Firefox 1+, Safari 3+, Chrome 1+, and Opera 8+.

+ +

Feedback and contribution are welcomed! Please join the +mailing list and read the +contribution guide +for further info.

+ +
+ + + +
+ + +
+Fork me on GitHub + + diff --git a/node_modules/esprima/package.json b/node_modules/esprima/package.json new file mode 100644 index 000000000..f0cc099db --- /dev/null +++ b/node_modules/esprima/package.json @@ -0,0 +1,31 @@ +{ + "name": "esprima", + "description": "ECMAScript parsing infrastructure for multipurpose analysis", + "homepage": "http://esprima.org", + "main": "esprima.js", + "bin": { + "esparse": "./bin/esparse.js" + }, + "version": "0.9.8", + "engines": { + "node": ">=0.4.0" + }, + "maintainers": [{ + "name": "Ariya Hidayat", + "email": "ariya.hidayat@gmail.com", + "web": "http://ariya.ofilabs.com" + }], + "repository": { + "type": "git", + "url": "http://github.com/ariya/esprima.git" + }, + "licenses": [{ + "type": "BSD", + "url": "http://github.com/ariya/esprima/raw/master/LICENSE.BSD" + }], + "scripts": { + "test": "node test/run.js", + "benchmark": "node test/benchmarks.js", + "benchmark-quick": "node test/benchmarks.js quick" + } +} diff --git a/node_modules/esprima/test/3rdparty/Tokenizer.js b/node_modules/esprima/test/3rdparty/Tokenizer.js new file mode 100644 index 000000000..8ca013ec8 --- /dev/null +++ b/node_modules/esprima/test/3rdparty/Tokenizer.js @@ -0,0 +1,646 @@ +if (typeof exports !== 'undefined') { + var window = {Unicode: require('./unicodecategories').Unicode}; + exports.Tokenizer = Tokenizer; +} + +/*! + * Tokenizer for JavaScript / ECMAScript 5 + * (c) Peter van der Zee, qfox.nl + */ + +/** + * @param {Object} inp + */ +function Tokenizer(inp){ + this.inp = inp||''; + // replace all other line terminators with \n (leave \r\n in tact though). we should probably remove the shadowInp when finished... + // only replace \r if it is not followed by a \n else \r\n would become \n\n causing a double newline where it is just a single + this.shadowInp = (inp||'').replace(Tokenizer.regexNormalizeNewlines, '\n'); + this.pos = 0; + this.line = 0; + this.column = 0; + this.cache = {}; + + this.errorStack = []; + + this.wtree = []; + this.btree = []; + +// this.regexWhiteSpace = Tokenizer.regexWhiteSpace; + this.regexLineTerminator = Tokenizer.regexLineTerminator; // used in fallback + this.regexAsciiIdentifier = Tokenizer.regexAsciiIdentifier; + this.hashAsciiIdentifier = Tokenizer.hashAsciiIdentifier; +// this.regexHex = Tokenizer.regexHex; + this.hashHex = Tokenizer.hashHex + this.regexUnicodeEscape = Tokenizer.regexUnicodeEscape; + this.regexIdentifierStop = Tokenizer.regexIdentifierStop; + this.hashIdentifierStop = Tokenizer.hashIdentifierStop; +// this.regexPunctuators = Tokenizer.regexPunctuators; + this.regexNumber = Tokenizer.regexNumber; + this.regexNewline = Tokenizer.regexNewline; + + this.regexBig = Tokenizer.regexBig; + this.regexBigAlt = Tokenizer.regexBigAlt; + + this.tokenCount = 0; + this.tokenCountNoWhite = 0; + + this.Unicode = window.Unicode; + + // if the Parser throws an error. it will set this property to the next match + // at the time of the error (which was not what it was expecting at that point) + // and pass on an "error" match. the error should be scooped on the stack and + // this property should be returned, without looking at the input... + this.errorEscape = null; +}; + +Tokenizer.prototype = { + inp:null, + shadowInp:null, + pos:null, + line:null, + column:null, + cache:null, + errorStack:null, + + wtree: null, // contains whitespace (spaces, comments, newlines) + btree: null, // does not contain any whitespace tokens. + + regexLineTerminator:null, + regexAsciiIdentifier:null, + hashAsciiIdentifier:null, + hashHex:null, + regexUnicodeEscape:null, + regexIdentifierStop:null, + hashIdentifierStop:null, + regexNumber:null, + regexNewline:null, + regexBig:null, + regexBigAlt:null, + tokenCount:null, + tokenCountNoWhite:null, + + Unicode:null, + + // storeCurrentAndFetchNextToken(bool, false, false true) to get just one token + storeCurrentAndFetchNextToken: function(noRegex, returnValue, stack, _dontStore){ + var regex = !noRegex; // TOFIX :) + var pos = this.pos; + var inp = this.inp; + var shadowInp = this.shadowInp; + var matchedNewline = false; + do { + if (!_dontStore) { + ++this.tokenCount; + stack.push(returnValue); + // did the parent Parser throw up? + if (this.errorEscape) { + returnValue = this.errorEscape; + this.errorEscape = null; + return returnValue; + } + } + _dontStore = false; + + if (pos >= inp.length) { + returnValue = {start:inp.length,stop:inp.length,name:12/*EOF*/}; + break; + } + var returnValue = null; + + var start = pos; + var chr = inp[pos]; + + // 1 ws 2 lt 3 scmt 4 mcmt 5/6 str 7 nr 8 rx 9 punc + //if (true) { + // substring method (I think this is faster..) + var part2 = inp.substring(pos,pos+4); + var part = this.regexBig.exec(part2); + //} else { + // // non-substring method (lastIndex) + // // this method does not need a substring to apply it + // this.regexBigAlt.lastIndex = pos; + // var part = this.regexBigAlt.exec(inp); + //} + + if (part[1]) { //this.regexWhiteSpace.test(chr)) { // SP, TAB, VT, FF, NBSP, BOM (, TOFIX: USP) + ++pos; + returnValue = {start:start,stop:pos,name:9/*WHITE_SPACE*/,line:this.line,col:this.column,isWhite:true}; + ++this.column; + } else if (part[2]) { //this.regexLineTerminator.test(chr)) { // LF, CR, LS, PS + var end = pos+1; + if (chr=='\r' && inp[pos+1] == '\n') ++end; // support crlf=>lf + returnValue = {start:pos,stop:end,name:10/*LINETERMINATOR*/,line:this.line,col:this.column,isWhite:true}; + pos = end; + // mark newlines for ASI + matchedNewline = true; + ++this.line; + this.column = 0; + returnValue.hasNewline = 1; + } else if (part[3]) { //chr == '/' && inp[pos+1] == '/') { + pos = shadowInp.indexOf('\n',pos); + if (pos == -1) pos = inp.length; + returnValue = {start:start,stop:pos,name:7/*COMMENT_SINGLE*/,line:this.line,col:this.column,isComment:true,isWhite:true}; + this.column = returnValue.stop; + } else if (part[4]) { //chr == '/' && inp[pos+1] == '*') { + var newpos = inp.indexOf('*/',pos); + if (newpos == -1) { + newpos = shadowInp.indexOf('\n', pos); + if (newpos < 0) pos += 2; + else pos = newpos; + returnValue = {start:start,stop:pos,name:14/*error*/,value:inp.substring(start, pos),line:this.line,col:this.column,isComment:true,isWhite:true,tokenError:true,error:Tokenizer.Error.UnterminatedMultiLineComment}; + this.errorStack.push(returnValue); + } else { + pos = newpos+2; + returnValue = {start:start,stop:pos,name:8/*COMMENT_MULTI*/,value:inp.substring(start, pos),line:this.line,col:this.column,isComment:true,isWhite:true}; + + // multi line comments are also reason for asi, but only if they contain at least one newline (use shadow input, because all line terminators would be valid...) + var shadowValue = shadowInp.substring(start, pos); + var i = 0, hasNewline = 0; + while (i < (i = shadowValue.indexOf('\n', i+1))) { + ++hasNewline; + } + if (hasNewline) { + matchedNewline = true; + returnValue.hasNewline = hasNewline; + this.line += hasNewline; + this.column = 0; + } else { + this.column = returnValue.stop; + } + } + } else if (part[5]) { //chr == "'") { + // old method + //console.log("old method"); + + var hasNewline = 0; + do { + // process escaped characters + while (pos < inp.length && inp[++pos] == '\\') { + if (shadowInp[pos+1] == '\n') ++hasNewline; + ++pos; + } + if (this.regexLineTerminator.test(inp[pos])) { + returnValue = {start:start,stop:pos,name:14/*error*/,value:inp.substring(start, pos),isString:true,tokenError:true,error:Tokenizer.Error.UnterminatedDoubleStringNewline}; + this.errorStack.push(returnValue); + break; + } + } while (pos < inp.length && inp[pos] != "'"); + if (returnValue) {} // error + else if (inp[pos] != "'") { + returnValue = {start:start,stop:pos,name:14/*error*/,value:inp.substring(start, pos),isString:true,tokenError:true,error:Tokenizer.Error.UnterminatedDoubleStringOther}; + this.errorStack.push(returnValue); + } else { + ++pos; + returnValue = {start:start,stop:pos,name:5/*STRING_SINGLE*/,isPrimitive:true,isString:true}; + if (hasNewline) { + returnValue.hasNewline = hasNewline; + this.line += hasNewline; + this.column = 0; + } else { + this.column += (pos-start); + } + } + } else if (part[6]) { //chr == '"') { + var hasNewline = 0; + // TODO: something like this: var regexmatch = /([^\']|$)+/.match(); + do { + // process escaped chars + while (pos < inp.length && inp[++pos] == '\\') { + if (shadowInp[pos+1] == '\n') ++hasNewline; + ++pos; + } + if (this.regexLineTerminator.test(inp[pos])) { + returnValue = {start:start,stop:pos,name:14/*error*/,value:inp.substring(start, pos),isString:true,tokenError:true,error:Tokenizer.Error.UnterminatedSingleStringNewline}; + this.errorStack.push(returnValue); + break; + } + } while (pos < inp.length && inp[pos] != '"'); + if (returnValue) {} + else if (inp[pos] != '"') { + returnValue = {start:start,stop:pos,name:14/*error*/,value:inp.substring(start, pos),isString:true,tokenError:true,error:Tokenizer.Error.UnterminatedSingleStringOther}; + this.errorStack.push(returnValue); + } else { + ++pos; + returnValue = {start:start,stop:pos,name:6/*STRING_DOUBLE*/,isPrimitive:true,isString:true}; + if (hasNewline) { + returnValue.hasNewline = hasNewline; + this.line += hasNewline; + this.column = 0; + } else { + this.column += (pos-start); + } + } + } else if (part[7]) { //(chr >= '0' && chr <= '9') || (chr == '.' && inp[pos+1] >= '0' && inp[pos+1] <= '9')) { + var nextPart = inp.substring(pos, pos+30); + var match = nextPart.match(this.regexNumber); + if (match[2]) { // decimal + var value = match[2]; + var parsingOctal = value[0] == '0' && value[1] && value[1] != 'e' && value[1] != 'E' && value[1] != '.'; + if (parsingOctal) { + returnValue = {start:start,stop:pos,name:14/*error*/,isNumber:true,isOctal:true,tokenError:true,error:Tokenizer.Error.IllegalOctalEscape,value:value}; + this.errorStack.push(returnValue); + } else { + returnValue = {start:start,stop:start+value.length,name:4/*NUMERIC_DEC*/,isPrimitive:true,isNumber:true,value:value}; + } + } else if (match[1]) { // hex + var value = match[1]; + returnValue = {start:start,stop:start+value.length,name:3/*NUMERIC_HEX*/,isPrimitive:true,isNumber:true,value:value}; + } else { + throw 'unexpected parser errror... regex fail :('; + } + + if (value.length < 300) { + pos += value.length; + } else { + // old method of parsing numbers. only used for extremely long number literals (300+ chars). + // this method does not require substringing... just memory :) + var tmpReturnValue = this.oldNumberParser(pos, chr, inp, returnValue, start, Tokenizer); + pos = tmpReturnValue[0]; + returnValue = tmpReturnValue[1]; + } + } else if (regex && part[8]) { //chr == '/') { // regex cannot start with /* (would be multiline comment, and not make sense anyways). but if it was /* then an earlier if would have eated it. so we only check for / + var twinfo = []; // matching {[( info + var found = false; + var parens = []; + var nonLethalError = null; + while (++pos < inp.length) { + chr = shadowInp[pos]; + // parse RegularExpressionChar + if (chr == '\n') { + returnValue = {start:start,stop:pos,name:14/*error*/,tokenError:true,errorHasContent:true,error:Tokenizer.Error.UnterminatedRegularExpressionNewline}; + this.errorStack.push(returnValue); + break; // fail + } else if (chr == '/') { + found = true; + break; + } else if (chr == '?' || chr == '*' || chr == '+') { + nonLethalError = Tokenizer.Error.NothingToRepeat; + } else if (chr == '^') { + if ( + inp[pos-1] != '/' && + inp[pos-1] != '|' && + inp[pos-1] != '(' && + !(inp[pos-3] == '(' && inp[pos-2] == '?' && (inp[pos-1] == ':' || inp[pos-1] == '!' || inp[pos-1] == '=')) + ) { + nonLethalError = Tokenizer.Error.StartOfMatchShouldBeAtStart; + } + } else if (chr == '$') { + if (inp[pos+1] != '/' && inp[pos+1] != '|' && inp[pos+1] != ')') nonLethalError = Tokenizer.Error.DollarShouldBeEnd; + } else if (chr == '}') { + nonLethalError = Tokenizer.Error.MissingOpeningCurly; + } else { // it's a "character" (can be group or class), something to match + // match parenthesis + if (chr == '(') { + parens.push(pos-start); + } else if (chr == ')') { + if (parens.length == 0) { + nonLethalError = {start:start,stop:pos,name:14/*error*/,tokenError:true,error:Tokenizer.Error.RegexNoOpenGroups}; + } else { + var twin = parens.pop(); + var now = pos-start; + twinfo[twin] = now; + twinfo[now] = twin; + } + } + // first process character class + if (chr == '[') { + var before = pos-start; + while (++pos < inp.length && shadowInp[pos] != '\n' && inp[pos] != ']') { + // only newline is not allowed in class range + // anything else can be escaped, most of it does not have to be escaped... + if (inp[pos] == '\\') { + if (shadowInp[pos+1] == '\n') break; + else ++pos; // skip next char. (mainly prohibits ] to be picked up as closing the group...) + } + } + if (inp[pos] != ']') { + returnValue = {start:start,stop:pos,name:14/*error*/,tokenError:true,error:Tokenizer.Error.ClosingClassRangeNotFound}; + this.errorStack.push(returnValue); + break; + } else { + var after = pos-start; + twinfo[before] = after; + twinfo[after] = before; + } + } else if (chr == '\\' && shadowInp[pos+1] != '\n') { + // is ok anywhere in the regex (match next char literally, regardless of its otherwise special meaning) + ++pos; + } + + // now process repeaters (+, ? and *) + + // non-collecting group (?:...) and positive (?=...) or negative (?!...) lookahead + if (chr == '(') { + if (inp[pos+1] == '?' && (inp[pos+2] == ':' || inp[pos+2] == '=' || inp[pos+2] == '!')) { + pos += 2; + } + } + // matching "char" + else if (inp[pos+1] == '?') ++pos; + else if (inp[pos+1] == '*' || inp[pos+1] == '+') { + ++pos; + if (inp[pos+1] == '?') ++pos; // non-greedy match + } else if (inp[pos+1] == '{') { + pos += 1; + var before = pos-start; + // quantifier: + // - {n} + // - {n,} + // - {n,m} + if (!/[0-9]/.test(inp[pos+1])) { + nonLethalError = Tokenizer.Error.QuantifierRequiresNumber; + } + while (++pos < inp.length && /[0-9]/.test(inp[pos+1])); + if (inp[pos+1] == ',') { + ++pos; + while (pos < inp.length && /[0-9]/.test(inp[pos+1])) ++pos; + } + if (inp[pos+1] != '}') { + nonLethalError = Tokenizer.Error.QuantifierRequiresClosingCurly; + } else { + ++pos; + var after = pos-start; + twinfo[before] = after; + twinfo[after] = before; + if (inp[pos+1] == '?') ++pos; // non-greedy match + } + } + } + } + // if found=false, fail right now. otherwise try to parse an identifiername (that's all RegularExpressionFlags is..., but it's constructed in a stupid fashion) + if (!found || returnValue) { + if (!returnValue) { + returnValue = {start:start,stop:pos,name:14/*error*/,tokenError:true,error:Tokenizer.Error.UnterminatedRegularExpressionOther}; + this.errorStack.push(returnValue); + } + } else { + // this is the identifier scanner, for now + do ++pos; + while (pos < inp.length && this.hashAsciiIdentifier[inp[pos]]); /*this.regexAsciiIdentifier.test(inp[pos])*/ + + if (parens.length) { + // nope, this is still an error, there was at least one paren that did not have a matching twin + if (parens.length > 0) returnValue = {start:start,stop:pos,name:14/*error*/,tokenError:true,error:Tokenizer.Error.RegexOpenGroup}; + this.errorStack.push(returnValue); + } else if (nonLethalError) { + returnValue = {start:start,stop:pos,name:14/*error*/,errorHasContent:true,tokenError:true,error:nonLethalError}; + this.errorStack.push(returnValue); + } else { + returnValue = {start:start,stop:pos,name:1/*REG_EX*/,isPrimitive:true}; + } + } + returnValue.twinfo = twinfo; + } else { + // note: operators need to be ordered from longest to smallest. regex will take care of the rest. + // no need to worry about div vs regex. if looking for regex, earlier if will have eaten it + //var result = this.regexPunctuators.exec(inp.substring(pos,pos+4)); + + // note: due to the regex, the single forward slash might be caught by an earlier part of the regex. so check for that. + var result = part[8] || part[9]; + if (result) { + //result = result[1]; + returnValue = {start:pos,stop:pos+=result.length,name:11/*PUNCTUATOR*/,value:result}; + } else { + var found = false; + // identifiers cannot start with a number. but if the leading string would be a number, another if would have eaten it already for numeric literal :) + while (pos < inp.length) { + var c = inp[pos]; + + if (this.hashAsciiIdentifier[c]) ++pos; //if (this.regexAsciiIdentifier.test(c)) ++pos; + else if (c == '\\' && this.regexUnicodeEscape.test(inp.substring(pos,pos+6))) pos += 6; // this is like a \uxxxx + // ok, now test unicode ranges... + // basically this hardly ever happens so there's little risk of this hitting performance + // however, if you do happen to have used them, it's not a problem. the parser will support it :) + else if (this.Unicode) { // the unicode is optional. + // these chars may not be part of identifier. i want to try to prevent running the unicode regexes here... + if (this.hashIdentifierStop[c] /*this.regexIdentifierStop.test(c)*/) break; + // for most scripts, the code wont reach here. which is good, because this is going to be relatively slow :) + var Unicode = this.Unicode; // cache + if (!( + // these may all occur in an identifier... (pure a specification compliance thing :) + Unicode.Lu.test(c) || Unicode.Ll.test(c) || Unicode.Lt.test(c) || Unicode.Lm.test(c) || + Unicode.Lo.test(c) || Unicode.Nl.test(c) || Unicode.Mn.test(c) || Unicode.Mc.test(c) || + Unicode.Nd.test(c) || Unicode.Pc.test(c) || Unicode.sp.test(c) + )) break; // end of match. + // passed, next char + ++pos; + } else break; // end of match. + + found = true; + } + + if (found) { + returnValue = {start:start,stop:pos,name:2/*IDENTIFIER*/,value:inp.substring(start,pos)}; + if (returnValue.value == 'undefined' || returnValue.value == 'null' || returnValue.value == 'true' || returnValue.value == 'false') returnValue.isPrimitive = true; + } else { + if (inp[pos] == '`') { + returnValue = {start:start,stop:pos+1,name:14/*error*/,tokenError:true,error:Tokenizer.Error.BacktickNotSupported}; + this.errorStack.push(returnValue); + } else if (inp[pos] == '\\') { + if (inp[pos+1] == 'u') { + returnValue = {start:start,stop:pos+1,name:14/*error*/,tokenError:true,error:Tokenizer.Error.InvalidUnicodeEscape}; + this.errorStack.push(returnValue); + } else { + returnValue = {start:start,stop:pos+1,name:14/*error*/,tokenError:true,error:Tokenizer.Error.InvalidBackslash}; + this.errorStack.push(returnValue); + } + } else { + returnValue = {start:start,stop:pos+1,name:14/*error*/,tokenError:true,error:Tokenizer.Error.Unknown,value:c}; + this.errorStack.push(returnValue); + // try to skip this char. it's not going anywhere. + } + ++pos; + } + } + } + + if (returnValue) { + // note that ASI's are slipstreamed in here from the parser since the tokenizer cant determine that + // if this part ever changes, make sure you change that too :) + returnValue.tokposw = this.wtree.length; + this.wtree.push(returnValue); + if (!returnValue.isWhite) { + returnValue.tokposb = this.btree.length; + this.btree.push(returnValue); + } + } + + + } while (stack && returnValue && returnValue.isWhite); // WHITE_SPACE LINETERMINATOR COMMENT_SINGLE COMMENT_MULTI + ++this.tokenCountNoWhite; + + this.pos = pos; + + if (matchedNewline) returnValue.newline = true; + return returnValue; + }, + addTokenToStreamBefore: function(token, match){ + var wtree = this.wtree; + var btree = this.btree; + if (match.name == 12/*asi*/) { + token.tokposw = wtree.length; + wtree.push(token); + token.tokposb = btree.length; + btree.push(token); + } else { + token.tokposw = match.tokposw; + wtree[token.tokposw] = token; + match.tokposw += 1; + wtree[match.tokposw] = match; + + if (match.tokposb) { + token.tokposb = match.tokposb; + btree[token.tokposb] = token; + match.tokposb += 1; + btree[match.tokposb] = match; + } + } + }, + oldNumberParser: function(pos, chr, inp, returnValue, start, Tokenizer){ + ++pos; + // either: 0x 0X 0 .3 + if (chr == '0' && (inp[pos] == 'x' || inp[pos] == 'X')) { + // parsing hex + while (++pos < inp.length && this.hashHex[inp[pos]]); // this.regexHex.test(inp[pos])); + returnValue = {start:start,stop:pos,name:3/*NUMERIC_HEX*/,isPrimitive:true,isNumber:true}; + } else { + var parsingOctal = chr == '0' && inp[pos] >= '0' && inp[pos] <= '9'; + // parsing dec + if (chr != '.') { // integer part + while (pos < inp.length && inp[pos] >= '0' && inp[pos] <= '9') ++pos; + if (inp[pos] == '.') ++pos; + } + // decimal part + while (pos < inp.length && inp[pos] >= '0' && inp[pos] <= '9') ++pos; + // exponent part + if (inp[pos] == 'e' || inp[pos] == 'E') { + if (inp[++pos] == '+' || inp[pos] == '-') ++pos; + var expPosBak = pos; + while (pos < inp.length && inp[pos] >= '0' && inp[pos] <= '9') ++pos; + if (expPosBak == pos) { + returnValue = {start:start,stop:pos,name:14/*error*/,tokenError:true,error:Tokenizer.Error.NumberExponentRequiresDigits}; + this.errorStack.push(returnValue); + } + } + if (returnValue.name != 14/*error*/) { + if (parsingOctal) { + returnValue = {start:start,stop:pos,name:14/*error*/,isNumber:true,isOctal:true,tokenError:true,error:Tokenizer.Error.IllegalOctalEscape}; + this.errorStack.push(returnValue); + console.log("foo") + } else { + returnValue = {start:start,stop:pos,name:4/*NUMERIC_DEC*/,isPrimitive:true,isNumber:true}; + } + } + } + return [pos, returnValue]; + }, + tokens: function(arrx){ + arrx = arrx || []; + var n = 0; + var last; + var stack = []; + while ((last = this.storeCurrentAndFetchNextToken(!arrx[n++], false, false, true)) && last.name != 12/*EOF*/) stack.push(last); + return stack; + }, + fixValues: function(){ + this.wtree.forEach(function(t){ + if (!t.value) t.value = this.inp.substring(t.start, t.stop); + },this); + } +}; + +//#ifdef TEST_SUITE +Tokenizer.escape = function(s){ + return s.replace(/\n/g,'\\n').replace(/\t/g,'\\t').replace(/&/g,'&').replace(//g,'>').replace(/\uFFFF/g, '\\uFFFF').replace(/\s/g, function(s){ + // replace whitespace as is... + var ord = s.charCodeAt(0).toString(16); + switch (ord.length) { + case 1: ord = '000'+ord; break; + case 2: ord = '00'+ord; break; + case 3: ord = '0'+ord; break; + } + return '\\u'+ord; + }); +}; +Tokenizer.testSuite = function(arr){ + var out = document.createElement('pre'); + document.body.appendChild(out); + var debug = function(){ + var f = document.createElement('div'); + f.innerHTML = Array.prototype.slice.call(arguments).join(' '); + out.appendChild(f); + return arguments[0]; + }; + + debug("Running test suite...",arr.length,"tests"); + debug(' '); + var start = +new Date; + var ok = 0; + var fail = 0; + for (var i=0; iTest '+i+' ok:',desc); + ++ok; + } else { + debug('Test failed:',desc,'(found',result.length,'expected',outputLen+')'),console.log(desc, result); + ++fail; + } + debug(''+Tokenizer.escape(input)+''); + debug('
'); + } + debug("Tokenizer test suite finished ("+(+new Date - start)+' ms). ok:'+ok+', fail:'+fail); +}; +//#endif + +Tokenizer.regexWhiteSpace = /[ \t\u000B\u000C\u00A0\uFFFF]/; +Tokenizer.regexLineTerminator = /[\u000A\u000D\u2028\u2029]/; +Tokenizer.regexAsciiIdentifier = /[a-zA-Z0-9\$_]/; +Tokenizer.hashAsciiIdentifier = {_:1,$:1,a:1,b:1,c:1,d:1,e:1,f:1,g:1,h:1,i:1,j:1,k:1,l:1,m:1,n:1,o:1,p:1,q:1,r:1,s:1,t:1,u:1,v:1,w:1,x:1,y:1,z:1,A:1,B:1,C:1,D:1,E:1,F:1,G:1,H:1,I:1,J:1,K:1,L:1,M:1,N:1,O:1,P:1,Q:1,R:1,S:1,T:1,U:1,V:1,W:1,X:1,Y:1,Z:1,0:1,1:1,2:1,3:1,4:1,5:1,6:1,7:1,8:1,9:1}; +Tokenizer.regexHex = /[0-9A-Fa-f]/; +Tokenizer.hashHex = {0:1,1:1,2:1,3:1,4:1,5:1,6:1,7:1,8:1,9:1,a:1,b:1,c:1,d:1,e:1,f:1,A:1,B:1,C:1,D:1,E:1,F:1}; +Tokenizer.regexUnicodeEscape = /u[0-9A-Fa-f]{4}/; // the \ is already checked at usage... +Tokenizer.regexIdentifierStop = /[\>\=\!\|\<\+\-\&\*\%\^\/\{\}\(\)\[\]\.\;\,\~\?\:\ \t\n\\\'\"]/; +Tokenizer.hashIdentifierStop = {'>':1,'=':1,'!':1,'|':1,'<':1,'+':1,'-':1,'&':1,'*':1,'%':1,'^':1,'/':1,'{':1,'}':1,'(':1,')':1,'[':1,']':1,'.':1,';':1,',':1,'~':1,'?':1,':':1,'\\':1,'\'':1,'"':1,' ':1,'\t':1,'\n':1}; +Tokenizer.regexNewline = /\n/g; +//Tokenizer.regexPunctuators = /^(>>>=|===|!==|>>>|<<=|>>=|<=|>=|==|!=|\+\+|--|<<|>>|\&\&|\|\||\+=|-=|\*=|%=|\&=|\|=|\^=|\/=|\{|\}|\(|\)|\[|\]|\.|;|,|<|>|\+|-|\*|%|\||\&|\||\^|!|~|\?|:|=|\/)/; +Tokenizer.Unidocde = window.Unicode; +Tokenizer.regexNumber = /^(?:(0[xX][0-9A-Fa-f]+)|((?:(?:(?:(?:[0-9]+)(?:\.[0-9]*)?))|(?:\.[0-9]+))(?:[eE][-+]?[0-9]{1,})?))/; +Tokenizer.regexNormalizeNewlines = /(\u000D[^\u000A])|[\u2028\u2029]/; + +// 1 ws 2 lt 3 scmt 4 mcmt 5/6 str 7 nr 8 rx 9 punc +Tokenizer.regexBig = /^([ \t\u000B\u000C\u00A0\uFFFF])?([\u000A\u000D\u2028\u2029])?(\/\/)?(\/\*)?(')?(")?(\.?[0-9])?(?:(\/)[^=])?(>>>=|===|!==|>>>|<<=|>>=|<=|>=|==|!=|\+\+|--|<<|>>|\&\&|\|\||\+=|-=|\*=|%=|\&=|\|=|\^=|\/=|\{|\}|\(|\)|\[|\]|\.|;|,|<|>|\+|-|\*|%|\||\&|\||\^|!|~|\?|:|=|\/)?/; +Tokenizer.regexBigAlt = /([ \t\u000B\u000C\u00A0\uFFFF])?([\u000A\u000D\u2028\u2029])?(\/\/)?(\/\*)?(')?(")?(\.?[0-9])?(?:(\/)[^=])?(>>>=|===|!==|>>>|<<=|>>=|<=|>=|==|!=|\+\+|--|<<|>>|\&\&|\|\||\+=|-=|\*=|%=|\&=|\|=|\^=|\/=|\{|\}|\(|\)|\[|\]|\.|;|,|<|>|\+|-|\*|%|\||\&|\||\^|!|~|\?|:|=|\/)?/g; + +Tokenizer.Error = { + UnterminatedSingleStringNewline: {msg:'Newlines are not allowed in string literals'}, + UnterminatedSingleStringOther: {msg:'Unterminated single string'}, + UnterminatedDoubleStringNewline: {msg:'Newlines are not allowed in string literals'}, + UnterminatedDoubleStringOther: {msg:'Unterminated double string'}, + UnterminatedRegularExpressionNewline: {msg:'Newlines are not allowed in regular expressions'}, + NothingToRepeat: {msg:'Used a repeat character (*?+) in a regex without something prior to it to match'}, + ClosingClassRangeNotFound: {msg: 'Unable to find ] for class range'}, + RegexOpenGroup: {msg: 'Open group did not find closing parenthesis'}, + RegexNoOpenGroups: {msg: 'Closing parenthesis found but no group open'}, + UnterminatedRegularExpressionOther: {msg:'Unterminated regular expression'}, + UnterminatedMultiLineComment: {msg:'Unterminated multi line comment'}, + UnexpectedIdentifier: {msg:'Unexpected identifier'}, + IllegalOctalEscape: {msg:'Octal escapes are not valid'}, + Unknown: {msg:'Unknown input'}, // if this happens, my parser is bad :( + NumberExponentRequiresDigits: {msg:'Numbers with exponents require at least one digit after the `e`'}, + BacktickNotSupported: {msg:'The backtick is not used in js, maybe you copy/pasted from a fancy site/doc?'}, + InvalidUnicodeEscape: {msg:'Encountered an invalid unicode escape, must be followed by exactly four hex numbers'}, + InvalidBackslash: {msg:'Encountered a backslash where it not allowed'}, + StartOfMatchShouldBeAtStart: {msg: 'The ^ signifies the start of match but was not found at a start'}, + DollarShouldBeEnd: {msg: 'The $ signifies the stop of match but was not found at a stop'}, + QuantifierRequiresNumber: {msg:'Quantifier curly requires at least one digit before the comma'}, + QuantifierRequiresClosingCurly: {msg:'Quantifier curly requires to be closed'}, + MissingOpeningCurly: {msg:'Encountered closing quantifier curly without seeing an opening curly'} +}; diff --git a/node_modules/esprima/test/3rdparty/XMLHttpRequest.js b/node_modules/esprima/test/3rdparty/XMLHttpRequest.js new file mode 100644 index 000000000..9752e217e --- /dev/null +++ b/node_modules/esprima/test/3rdparty/XMLHttpRequest.js @@ -0,0 +1,509 @@ +/** +* XMLHttpRequest.js Copyright (C) 2011 Sergey Ilinsky (http://www.ilinsky.com) +* +* This work is free software; you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation; either version 2.1 of the License, or +* (at your option) any later version. +* +* This work is distributed in the hope that it will be useful, +* but without any warranty; without even the implied warranty of +* merchantability or fitness for a particular purpose. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this library; if not, write to the Free Software Foundation, Inc., +* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +(function () { + + // Save reference to earlier defined object implementation (if any) + var oXMLHttpRequest = window.XMLHttpRequest; + + // Define on browser type + var bGecko = !!window.controllers; + var bIE = window.document.all && !window.opera; + var bIE7 = bIE && window.navigator.userAgent.match(/MSIE 7.0/); + + // Enables "XMLHttpRequest()" call next to "new XMLHttpReques()" + function fXMLHttpRequest() { + this._object = oXMLHttpRequest && !bIE7 ? new oXMLHttpRequest : new window.ActiveXObject("Microsoft.XMLHTTP"); + this._listeners = []; + } + + // Constructor + function cXMLHttpRequest() { + return new fXMLHttpRequest; + } + cXMLHttpRequest.prototype = fXMLHttpRequest.prototype; + + // BUGFIX: Firefox with Firebug installed would break pages if not executed + if (bGecko && oXMLHttpRequest.wrapped) { + cXMLHttpRequest.wrapped = oXMLHttpRequest.wrapped; + } + + // Constants + cXMLHttpRequest.UNSENT = 0; + cXMLHttpRequest.OPENED = 1; + cXMLHttpRequest.HEADERS_RECEIVED = 2; + cXMLHttpRequest.LOADING = 3; + cXMLHttpRequest.DONE = 4; + + // Public Properties + cXMLHttpRequest.prototype.readyState = cXMLHttpRequest.UNSENT; + cXMLHttpRequest.prototype.responseText = ''; + cXMLHttpRequest.prototype.responseXML = null; + cXMLHttpRequest.prototype.status = 0; + cXMLHttpRequest.prototype.statusText = ''; + + // Priority proposal + cXMLHttpRequest.prototype.priority = "NORMAL"; + + // Instance-level Events Handlers + cXMLHttpRequest.prototype.onreadystatechange = null; + + // Class-level Events Handlers + cXMLHttpRequest.onreadystatechange = null; + cXMLHttpRequest.onopen = null; + cXMLHttpRequest.onsend = null; + cXMLHttpRequest.onabort = null; + + // Public Methods + cXMLHttpRequest.prototype.open = function(sMethod, sUrl, bAsync, sUser, sPassword) { + // Delete headers, required when object is reused + delete this._headers; + + // When bAsync parameter value is omitted, use true as default + if (arguments.length < 3) { + bAsync = true; + } + + // Save async parameter for fixing Gecko bug with missing readystatechange in synchronous requests + this._async = bAsync; + + // Set the onreadystatechange handler + var oRequest = this; + var nState = this.readyState; + var fOnUnload = null; + + // BUGFIX: IE - memory leak on page unload (inter-page leak) + if (bIE && bAsync) { + fOnUnload = function() { + if (nState != cXMLHttpRequest.DONE) { + fCleanTransport(oRequest); + // Safe to abort here since onreadystatechange handler removed + oRequest.abort(); + } + }; + window.attachEvent("onunload", fOnUnload); + } + + // Add method sniffer + if (cXMLHttpRequest.onopen) { + cXMLHttpRequest.onopen.apply(this, arguments); + } + + if (arguments.length > 4) { + this._object.open(sMethod, sUrl, bAsync, sUser, sPassword); + } else if (arguments.length > 3) { + this._object.open(sMethod, sUrl, bAsync, sUser); + } else { + this._object.open(sMethod, sUrl, bAsync); + } + + this.readyState = cXMLHttpRequest.OPENED; + fReadyStateChange(this); + + this._object.onreadystatechange = function() { + if (bGecko && !bAsync) { + return; + } + + // Synchronize state + oRequest.readyState = oRequest._object.readyState; + fSynchronizeValues(oRequest); + + // BUGFIX: Firefox fires unnecessary DONE when aborting + if (oRequest._aborted) { + // Reset readyState to UNSENT + oRequest.readyState = cXMLHttpRequest.UNSENT; + + // Return now + return; + } + + if (oRequest.readyState == cXMLHttpRequest.DONE) { + // Free up queue + delete oRequest._data; + + // Uncomment these lines for bAsync + /** + * if (bAsync) { + * fQueue_remove(oRequest); + * } + */ + + fCleanTransport(oRequest); + + // Uncomment this block if you need a fix for IE cache + /** + * // BUGFIX: IE - cache issue + * if (!oRequest._object.getResponseHeader("Date")) { + * // Save object to cache + * oRequest._cached = oRequest._object; + * + * // Instantiate a new transport object + * cXMLHttpRequest.call(oRequest); + * + * // Re-send request + * if (sUser) { + * if (sPassword) { + * oRequest._object.open(sMethod, sUrl, bAsync, sUser, sPassword); + * } else { + * oRequest._object.open(sMethod, sUrl, bAsync); + * } + * + * oRequest._object.setRequestHeader("If-Modified-Since", oRequest._cached.getResponseHeader("Last-Modified") || new window.Date(0)); + * // Copy headers set + * if (oRequest._headers) { + * for (var sHeader in oRequest._headers) { + * // Some frameworks prototype objects with functions + * if (typeof oRequest._headers[sHeader] == "string") { + * oRequest._object.setRequestHeader(sHeader, oRequest._headers[sHeader]); + * } + * } + * } + * oRequest._object.onreadystatechange = function() { + * // Synchronize state + * oRequest.readyState = oRequest._object.readyState; + * + * if (oRequest._aborted) { + * // + * oRequest.readyState = cXMLHttpRequest.UNSENT; + * + * // Return + * return; + * } + * + * if (oRequest.readyState == cXMLHttpRequest.DONE) { + * // Clean Object + * fCleanTransport(oRequest); + * + * // get cached request + * if (oRequest.status == 304) { + * oRequest._object = oRequest._cached; + * } + * + * // + * delete oRequest._cached; + * + * // + * fSynchronizeValues(oRequest); + * + * // + * fReadyStateChange(oRequest); + * + * // BUGFIX: IE - memory leak in interrupted + * if (bIE && bAsync) { + * window.detachEvent("onunload", fOnUnload); + * } + * + * } + * }; + * oRequest._object.send(null); + * + * // Return now - wait until re-sent request is finished + * return; + * }; + */ + + // BUGFIX: IE - memory leak in interrupted + if (bIE && bAsync) { + window.detachEvent("onunload", fOnUnload); + } + + // BUGFIX: Some browsers (Internet Explorer, Gecko) fire OPEN readystate twice + if (nState != oRequest.readyState) { + fReadyStateChange(oRequest); + } + + nState = oRequest.readyState; + } + }; + }; + + cXMLHttpRequest.prototype.send = function(vData) { + // Add method sniffer + if (cXMLHttpRequest.onsend) { + cXMLHttpRequest.onsend.apply(this, arguments); + } + + if (!arguments.length) { + vData = null; + } + + // BUGFIX: Safari - fails sending documents created/modified dynamically, so an explicit serialization required + // BUGFIX: IE - rewrites any custom mime-type to "text/xml" in case an XMLNode is sent + // BUGFIX: Gecko - fails sending Element (this is up to the implementation either to standard) + if (vData && vData.nodeType) { + vData = window.XMLSerializer ? new window.XMLSerializer().serializeToString(vData) : vData.xml; + if (!this._headers["Content-Type"]) { + this._object.setRequestHeader("Content-Type", "application/xml"); + } + } + + this._data = vData; + + /** + * // Add to queue + * if (this._async) { + * fQueue_add(this); + * } else { */ + fXMLHttpRequest_send(this); + /** + * } + */ + }; + + cXMLHttpRequest.prototype.abort = function() { + // Add method sniffer + if (cXMLHttpRequest.onabort) { + cXMLHttpRequest.onabort.apply(this, arguments); + } + + // BUGFIX: Gecko - unnecessary DONE when aborting + if (this.readyState > cXMLHttpRequest.UNSENT) { + this._aborted = true; + } + + this._object.abort(); + + // BUGFIX: IE - memory leak + fCleanTransport(this); + + this.readyState = cXMLHttpRequest.UNSENT; + + delete this._data; + + /* if (this._async) { + * fQueue_remove(this); + * } + */ + }; + + cXMLHttpRequest.prototype.getAllResponseHeaders = function() { + return this._object.getAllResponseHeaders(); + }; + + cXMLHttpRequest.prototype.getResponseHeader = function(sName) { + return this._object.getResponseHeader(sName); + }; + + cXMLHttpRequest.prototype.setRequestHeader = function(sName, sValue) { + // BUGFIX: IE - cache issue + if (!this._headers) { + this._headers = {}; + } + + this._headers[sName] = sValue; + + return this._object.setRequestHeader(sName, sValue); + }; + + // EventTarget interface implementation + cXMLHttpRequest.prototype.addEventListener = function(sName, fHandler, bUseCapture) { + for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++) { + if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture) { + return; + } + } + + // Add listener + this._listeners.push([sName, fHandler, bUseCapture]); + }; + + cXMLHttpRequest.prototype.removeEventListener = function(sName, fHandler, bUseCapture) { + for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++) { + if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture) { + break; + } + } + + // Remove listener + if (oListener) { + this._listeners.splice(nIndex, 1); + } + }; + + cXMLHttpRequest.prototype.dispatchEvent = function(oEvent) { + var oEventPseudo = { + 'type': oEvent.type, + 'target': this, + 'currentTarget': this, + 'eventPhase': 2, + 'bubbles': oEvent.bubbles, + 'cancelable': oEvent.cancelable, + 'timeStamp': oEvent.timeStamp, + 'stopPropagation': function() {}, // There is no flow + 'preventDefault': function() {}, // There is no default action + 'initEvent': function() {} // Original event object should be initialized + }; + + // Execute onreadystatechange + if (oEventPseudo.type == "readystatechange" && this.onreadystatechange) { + (this.onreadystatechange.handleEvent || this.onreadystatechange).apply(this, [oEventPseudo]); + } + + + // Execute listeners + for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++) { + if (oListener[0] == oEventPseudo.type && !oListener[2]) { + (oListener[1].handleEvent || oListener[1]).apply(this, [oEventPseudo]); + } + } + + }; + + // + cXMLHttpRequest.prototype.toString = function() { + return '[' + "object" + ' ' + "XMLHttpRequest" + ']'; + }; + + cXMLHttpRequest.toString = function() { + return '[' + "XMLHttpRequest" + ']'; + }; + + /** + * // Queue manager + * var oQueuePending = {"CRITICAL":[],"HIGH":[],"NORMAL":[],"LOW":[],"LOWEST":[]}, + * aQueueRunning = []; + * function fQueue_add(oRequest) { + * oQueuePending[oRequest.priority in oQueuePending ? oRequest.priority : "NORMAL"].push(oRequest); + * // + * setTimeout(fQueue_process); + * }; + * + * function fQueue_remove(oRequest) { + * for (var nIndex = 0, bFound = false; nIndex < aQueueRunning.length; nIndex++) + * if (bFound) { + * aQueueRunning[nIndex - 1] = aQueueRunning[nIndex]; + * } else { + * if (aQueueRunning[nIndex] == oRequest) { + * bFound = true; + * } + * } + * + * if (bFound) { + * aQueueRunning.length--; + * } + * + * + * // + * setTimeout(fQueue_process); + * }; + * + * function fQueue_process() { + * if (aQueueRunning.length < 6) { + * for (var sPriority in oQueuePending) { + * if (oQueuePending[sPriority].length) { + * var oRequest = oQueuePending[sPriority][0]; + * oQueuePending[sPriority] = oQueuePending[sPriority].slice(1); + * // + * aQueueRunning.push(oRequest); + * // Send request + * fXMLHttpRequest_send(oRequest); + * break; + * } + * } + * } + * }; + */ + + // Helper function + function fXMLHttpRequest_send(oRequest) { + oRequest._object.send(oRequest._data); + + // BUGFIX: Gecko - missing readystatechange calls in synchronous requests + if (bGecko && !oRequest._async) { + oRequest.readyState = cXMLHttpRequest.OPENED; + + // Synchronize state + fSynchronizeValues(oRequest); + + // Simulate missing states + while (oRequest.readyState < cXMLHttpRequest.DONE) { + oRequest.readyState++; + fReadyStateChange(oRequest); + // Check if we are aborted + if (oRequest._aborted) { + return; + } + } + } + } + + function fReadyStateChange(oRequest) { + // Sniffing code + if (cXMLHttpRequest.onreadystatechange){ + cXMLHttpRequest.onreadystatechange.apply(oRequest); + } + + + // Fake event + oRequest.dispatchEvent({ + 'type': "readystatechange", + 'bubbles': false, + 'cancelable': false, + 'timeStamp': new Date + 0 + }); + } + + function fGetDocument(oRequest) { + var oDocument = oRequest.responseXML; + var sResponse = oRequest.responseText; + // Try parsing responseText + if (bIE && sResponse && oDocument && !oDocument.documentElement && oRequest.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/)) { + oDocument = new window.ActiveXObject("Microsoft.XMLDOM"); + oDocument.async = false; + oDocument.validateOnParse = false; + oDocument.loadXML(sResponse); + } + + // Check if there is no error in document + if (oDocument){ + if ((bIE && oDocument.parseError !== 0) || !oDocument.documentElement || (oDocument.documentElement && oDocument.documentElement.tagName == "parsererror")) { + return null; + } + } + return oDocument; + } + + function fSynchronizeValues(oRequest) { + try { oRequest.responseText = oRequest._object.responseText; } catch (e) {} + try { oRequest.responseXML = fGetDocument(oRequest._object); } catch (e) {} + try { oRequest.status = oRequest._object.status; } catch (e) {} + try { oRequest.statusText = oRequest._object.statusText; } catch (e) {} + } + + function fCleanTransport(oRequest) { + // BUGFIX: IE - memory leak (on-page leak) + oRequest._object.onreadystatechange = new window.Function; + } + + // Internet Explorer 5.0 (missing apply) + if (!window.Function.prototype.apply) { + window.Function.prototype.apply = function(oRequest, oArguments) { + if (!oArguments) { + oArguments = []; + } + oRequest.__func = this; + oRequest.__func(oArguments[0], oArguments[1], oArguments[2], oArguments[3], oArguments[4]); + delete oRequest.__func; + }; + } + + // Register new object with window + window.XMLHttpRequest = cXMLHttpRequest; + +})(); diff --git a/node_modules/esprima/test/3rdparty/ZeParser.js b/node_modules/esprima/test/3rdparty/ZeParser.js new file mode 100644 index 000000000..c81af426d --- /dev/null +++ b/node_modules/esprima/test/3rdparty/ZeParser.js @@ -0,0 +1,2185 @@ +if (typeof exports !== 'undefined') { + var Tokenizer = require('./Tokenizer').Tokenizer; + exports.ZeParser = ZeParser; +} + +/** + * This is my js Parser: Ze. It's actually the post-dev pre-cleanup version. Clearly. + * Some optimizations have been applied :) + * (c) Peter van der Zee, qfox.nl + * @param {String} inp Input + * @param {Tokenizer} tok + * @param {Array} stack The tokens will be put in this array. If you're looking for the AST, this would be it :) + */ +function ZeParser(inp, tok, stack, simple){ + this.input = inp; + this.tokenizer = tok; + this.stack = stack; + this.stack.root = true; + this.scope = stack.scope = [{value:'this', isDeclared:true, isEcma:true, thisIsGlobal:true}]; // names of variables + this.scope.global = true; + this.statementLabels = []; + + this.errorStack = []; + + stack.scope = this.scope; // hook root + stack.labels = this.statementLabels; + + this.regexLhsStart = ZeParser.regexLhsStart; +/* + this.regexStartKeyword = ZeParser.regexStartKeyword; + this.regexKeyword = ZeParser.regexKeyword; + this.regexStartReserved = ZeParser.regexStartReserved; + this.regexReserved = ZeParser.regexReserved; +*/ + this.regexStartKeyOrReserved = ZeParser.regexStartKeyOrReserved; + this.hashStartKeyOrReserved = ZeParser.hashStartKeyOrReserved; + this.regexIsKeywordOrReserved = ZeParser.regexIsKeywordOrReserved; + this.regexAssignments = ZeParser.regexAssignments; + this.regexNonAssignmentBinaryExpressionOperators = ZeParser.regexNonAssignmentBinaryExpressionOperators; + this.regexUnaryKeywords = ZeParser.regexUnaryKeywords; + this.hashUnaryKeywordStart = ZeParser.hashUnaryKeywordStart; + this.regexUnaryOperators = ZeParser.regexUnaryOperators; + this.regexLiteralKeywords = ZeParser.regexLiteralKeywords; + this.testing = {'this':1,'null':1,'true':1,'false':1}; + + this.ast = !simple; ///#define FULL_AST +}; +/** + * Returns just a stacked parse tree (regular array) + * @param {string} input + * @param {boolean} simple=false + * @return {Array} + */ +ZeParser.parse = function(input, simple){ + var tok = new Tokenizer(input); + var stack = []; + try { + var parser = new ZeParser(input, tok, stack); + if (simple) parser.ast = false; + parser.parse(); + return stack; + } catch (e) { + console.log("Parser has a bug for this input, please report it :)", e); + return null; + } +}; +/** + * Returns a new parser instance with parse details for input + * @param {string} input + * @returns {ZeParser} + */ +ZeParser.createParser = function(input){ + var tok = new Tokenizer(input); + var stack = []; + try { + var parser = new ZeParser(input, tok, stack); + parser.parse(); + return parser; + } catch (e) { + console.log("Parser has a bug for this input, please report it :)", e); + return null; + } +}; +ZeParser.prototype = { + input: null, + tokenizer: null, + stack: null, + scope: null, + statementLabels: null, + errorStack: null, + + ast: null, + + parse: function(match){ + if (match) match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, this.stack); // meh + else match = this.tokenizer.storeCurrentAndFetchNextToken(false, null, this.stack, true); // initialization step, dont store the match (there isnt any!) + + match = this.eatSourceElements(match, this.stack); + + var cycled = false; + do { + if (match && match.name != 12/*eof*/) { + // if not already an error, insert an error before it + if (match.name != 14/*error*/) this.failignore('UnexpectedToken', match, this.stack); + // just parse the token as is and continue. + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, this.stack); + cycled = true; + } + + // keep gobbling any errors... + } while (match && match.name == 14/*error*/); + + // now try again (but only if we gobbled at least one token)... + if (cycled && match && match.name != 12/*eof*/) match = this.parse(match); + + // pop the last token off the stack if it caused an error at eof + if (this.tokenizer.errorEscape) { + this.stack.push(this.tokenizer.errorEscape); + this.tokenizer.errorEscape = null; + } + + return match; + }, + + eatSemiColon: function(match, stack){ + //this.stats.eatSemiColon = (+//this.stats.eatSemiColon||0)+1; + if (match.value == ';') match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + else { + // try asi + // only if: + // - this token was preceeded by at least one newline (match.newline) or next token is } + // - this is EOF + // - prev token was one of return,continue,break,throw (restricted production), not checked here. + + // the exceptions to this rule are + // - if the next line is a regex + // - the semi is part of the for-header. + // these exceptions are automatically caught by the way the parser is built + + // not eof and just parsed semi or no newline preceeding and next isnt } + if (match.name != 12/*EOF*/ && (match.semi || (!match.newline && match.value != '}')) && !(match.newline && (match.value == '++' || match.value == '--'))) { + this.failignore('NoASI', match, stack); + } else { + // ASI + // (match is actually the match _after_ this asi, so the position of asi is match.start, not stop (!) + var asi = {start:match.start,stop:match.start,name:13/*ASI*/}; + stack.push(asi); + + // slip it in the stream, before the current match. + // for the other tokens see the tokenizer near the end of the main parsing function + this.tokenizer.addTokenToStreamBefore(asi, match); + } + } + match.semi = true; + return match; + }, + /** + * Eat one or more "AssignmentExpression"s. May also eat a labeled statement if + * the parameters are set that way. This is the only way to linearly distinct between + * an expression-statement and a labeled-statement without double lookahead. (ok, maybe not "only") + * @param {boolean} mayParseLabeledStatementInstead=false If the first token is an identifier and the second a colon, accept this match as a labeled statement instead... Only true if the match in the parameter is an (unreserved) identifier (so no need to validate that further) + * @param {Object} match + * @param {Array} stack + * @param {boolean} onlyOne=false Only parse a AssignmentExpression + * @param {boolean} forHeader=false Do not allow the `in` operator + * @param {boolean} isBreakOrContinueArg=false The argument for break or continue is always a single identifier + * @return {Object} + */ + eatExpressions: function(mayParseLabeledStatementInstead, match, stack, onlyOne, forHeader, isBreakOrContinueArg){ + if (this.ast) { //#ifdef FULL_AST + var pstack = stack; + stack = []; + stack.desc = 'expressions'; + stack.nextBlack = match.tokposb; + pstack.push(stack); + + var parsedExpressions = 0; + } //#endif + + var first = true; + do { + var parsedNonAssignmentOperator = false; // once we parse a non-assignment, this expression can no longer parse an assignment + // TOFIX: can probably get the regex out somehow... + if (!first) { + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (!(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value))) match = this.failsafe('ExpectedAnotherExpressionComma', match); + } + + if (this.ast) { //#ifdef FULL_AST + ++parsedExpressions; + + var astack = stack; + stack = []; + stack.desc = 'expression'; + stack.nextBlack = match.tokposb; + astack.push(stack); + } //#endif + + // start of expression is given: match + // it should indeed be a properly allowed lhs + // first eat all unary operators + // they can be added to the stack, but we need to ensure they have indeed a valid operator + + var parseAnotherExpression = true; + while (parseAnotherExpression) { // keep parsing lhs+operator as long as there is an operator after the lhs. + if (this.ast) { //#ifdef FULL_AST + var estack = stack; + stack = []; + stack.desc = 'sub-expression'; + stack.nextBlack = match.tokposb; + estack.push(stack); + + var news = 0; // encountered new operators waiting for parenthesis + } //#endif + + // start checking lhs + // if lhs is identifier (new/call expression), allow to parse an assignment operator next + // otherwise keep eating unary expressions and then any "value" + // after that search for a binary operator. if we only ate a new/call expression then + // also allow to eat assignments. repeat for the rhs. + var parsedUnaryOperator = false; + var isUnary = null; + while ( + !isBreakOrContinueArg && // no unary for break/continue + (isUnary = + (match.value && this.hashUnaryKeywordStart[match.value[0]] && this.regexUnaryKeywords.test(match.value)) || // (match.value == 'delete' || match.value == 'void' || match.value == 'typeof' || match.value == 'new') || + (match.name == 11/*PUNCTUATOR*/ && this.regexUnaryOperators.test(match.value)) + ) + ) { + if (isUnary) match.isUnaryOp = true; + if (this.ast) { //#ifdef FULL_AST + // find parenthesis + if (match.value == 'new') ++news; + } //#endif + + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + // ensure that it is in fact a valid lhs-start + if (!(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value))) match = this.failsafe('ExpectedAnotherExpressionRhs', match); + // not allowed to parse assignment + parsedUnaryOperator = true; + }; + + // if we parsed any kind of unary operator, we cannot be parsing a labeled statement + if (parsedUnaryOperator) mayParseLabeledStatementInstead = false; + + // so now we know match is a valid lhs-start and not a unary operator + // it must be a string, number, regex, identifier + // or the start of an object literal ({), array literal ([) or group operator ((). + + var acceptAssignment = false; + + // take care of the "open" cases first (group, array, object) + if (match.value == '(') { + if (this.ast) { //#ifdef FULL_AST + var groupStack = stack; + stack = []; + stack.desc = 'grouped'; + stack.nextBlack = match.tokposb; + groupStack.push(stack); + + var lhp = match; + + match.isGroupStart = true; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (!(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value))) match = this.failsafe('GroupingShouldStartWithExpression', match); + // keep parsing expressions as long as they are followed by a comma + match = this.eatExpressions(false, match, stack); + + if (match.value != ')') match = this.failsafe('UnclosedGroupingOperator', match); + if (this.ast) { //#ifdef FULL_AST + match.twin = lhp; + lhp.twin = match; + + match.isGroupStop = true; + + if (stack[stack.length-1].desc == 'expressions') { + // create ref to this expression group to the opening paren + lhp.expressionArg = stack[stack.length-1]; + } + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(true, match, stack); // might be div + + if (this.ast) { //#ifdef FULL_AST + stack = groupStack; + } //#endif + // you can assign to group results. and as long as the group does not contain a comma (and valid ref), it will work too :) + acceptAssignment = true; + // there's an extra rule for [ namely that, it must start with an expression but after that, expressions are optional + } else if (match.value == '[') { + if (this.ast) { //#ifdef FULL_AST + stack.sub = 'array literal'; + stack.hasArrayLiteral = true; + var lhsb = match; + + match.isArrayLiteralStart = true; + + if (!this.scope.arrays) this.scope.arrays = []; + match.arrayId = this.scope.arrays.length; + this.scope.arrays.push(match); + + match.targetScope = this.scope; + } //#endif + // keep parsing expressions as long as they are followed by a comma + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + + // arrays may start with "elided" commas + while (match.value == ',') match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + + var foundAtLeastOneComma = true; // for entry in while + while (foundAtLeastOneComma && match.value != ']') { + foundAtLeastOneComma = false; + + if (!(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value)) && match.name != 14/*error*/) match = this.failsafe('ArrayShouldStartWithExpression', match); + match = this.eatExpressions(false, match, stack, true); + + while (match.value == ',') { + foundAtLeastOneComma = true; + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + } + } + if (match.value != ']') { + match = this.failsafe('UnclosedPropertyBracket', match); + } + if (this.ast) { //#ifdef FULL_AST + match.twin = lhsb; + lhsb.twin = match; + + match.isArrayLiteralStop = true; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(true, match, stack); // might be div + while (match.value == '++' || match.value == '--') { + // gobble and ignore? + this.failignore('InvalidPostfixOperandArray', match, stack); + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + } + // object literals need seperate handling... + } else if (match.value == '{') { + if (this.ast) { //#ifdef FULL_AST + stack.sub = 'object literal'; + stack.hasObjectLiteral = true; + + match.isObjectLiteralStart = true; + + if (!this.scope.objects) this.scope.objects = []; + match.objectId = this.scope.objects.length; + this.scope.objects.push(match); + + var targetObject = match; + match.targetScope = this.scope; + + var lhc = match; + } //#endif + + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.name == 12/*eof*/) { + match = this.failsafe('ObjectLiteralExpectsColonAfterName', match); + } + // ObjectLiteral + // PropertyNameAndValueList + + while (match.value != '}' && match.name != 14/*error*/) { // will stop if next token is } or throw if not and no comma is found + // expecting a string, number, or identifier + //if (match.name != 5/*STRING_SINGLE*/ && match.name != 6/*STRING_DOUBLE*/ && match.name != 3/*NUMERIC_HEX*/ && match.name != 4/*NUMERIC_DEC*/ && match.name != 2/*IDENTIFIER*/) { + // TOFIX: more specific errors depending on type... + if (!match.isNumber && !match.isString && match.name != 2/*IDENTIFIER*/) { + match = this.failsafe('IllegalPropertyNameToken', match); + } + + if (this.ast) { //#ifdef FULL_AST + var objLitStack = stack; + stack = []; + stack.desc = 'objlit pair'; + stack.isObjectLiteralPair = true; + stack.nextBlack = match.tokposb; + objLitStack.push(stack); + + var propNameStack = stack; + stack = []; + stack.desc = 'objlit pair name'; + stack.nextBlack = match.tokposb; + propNameStack.push(stack); + + propNameStack.sub = 'data'; + + var propName = match; + propName.isPropertyName = true; + } //#endif + + var getset = match.value; + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (this.ast) { //#ifdef FULL_AST + stack = propNameStack; + } //#endif + + // for get/set we parse a function-like definition. but only if it's immediately followed by an identifier (otherwise it'll just be the property 'get' or 'set') + if (getset == 'get') { + // "get" PropertyName "(" ")" "{" FunctionBody "}" + if (match.value == ':') { + if (this.ast) { //#ifdef FULL_AST + propName.isPropertyOf = targetObject; + } //#endif + match = this.eatObjectLiteralColonAndBody(match, stack); + } else { + if (this.ast) { //#ifdef FULL_AST + match.isPropertyOf = targetObject; + propNameStack.sub = 'getter'; + propNameStack.isAccessor = true; + } //#endif + // if (match.name != 2/*IDENTIFIER*/ && match.name != 5/*STRING_SINGLE*/ && match.name != 6/*STRING_DOUBLE*/ && match.name != 3/*NUMERIC_HEX*/ && match.name != 4/*NUMERIC_DEC*/) { + if (!match.isNumber && !match.isString && match.name != 2/*IDENTIFIER*/) match = this.failsafe('IllegalGetterSetterNameToken', match, true); + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.value != '(') match = this.failsafe('GetterSetterNameFollowedByOpenParen', match); + if (this.ast) { //#ifdef FULL_AST + var lhp = match; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.value != ')') match = this.failsafe('GetterHasNoArguments', match); + if (this.ast) { //#ifdef FULL_AST + match.twin = lhp; + lhp.twin = match; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + match = this.eatFunctionBody(match, stack); + } + } else if (getset == 'set') { + // "set" PropertyName "(" PropertySetParameterList ")" "{" FunctionBody "}" + if (match.value == ':') { + if (this.ast) { //#ifdef FULL_AST + propName.isPropertyOf = targetObject; + } //#endif + match = this.eatObjectLiteralColonAndBody(match, stack); + } else { + if (this.ast) { //#ifdef FULL_AST + match.isPropertyOf = targetObject; + propNameStack.sub = 'setter'; + propNameStack.isAccessor = true; + } //#endif + if (!match.isNumber && !match.isString && match.name != 2/*IDENTIFIER*/) match = this.failsafe('IllegalGetterSetterNameToken', match); + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.value != '(') match = this.failsafe('GetterSetterNameFollowedByOpenParen', match); + if (this.ast) { //#ifdef FULL_AST + var lhp = match; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.name != 2/*IDENTIFIER*/) { + if (match.value == ')') match = this.failsafe('SettersMustHaveArgument', match); + else match = this.failsafe('IllegalSetterArgumentNameToken', match); + } + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.value != ')') { + if (match.value == ',') match = this.failsafe('SettersOnlyGetOneArgument', match); + else match = this.failsafe('SetterHeaderShouldHaveClosingParen', match); + } + if (this.ast) { //#ifdef FULL_AST + match.twin = lhp; + lhp.twin = match; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + match = this.eatFunctionBody(match, stack); + } + } else { + // PropertyName ":" AssignmentExpression + if (this.ast) { //#ifdef FULL_AST + propName.isPropertyOf = targetObject; + } //#endif + match = this.eatObjectLiteralColonAndBody(match, stack); + } + + if (this.ast) { //#ifdef FULL_AST + stack = objLitStack; + } //#endif + + // one trailing comma allowed + if (match.value == ',') { + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.value == ',') match = this.failsafe('IllegalDoubleCommaInObjectLiteral', match); + } else if (match.value != '}') match = this.failsafe('UnclosedObjectLiteral', match); + + // either the next token is } and the loop breaks or + // the next token is the start of the next PropertyAssignment... + } + // closing curly + if (this.ast) { //#ifdef FULL_AST + match.twin = lhc; + lhc.twin = match; + + match.isObjectLiteralStop = true; + } //#endif + + match = this.tokenizer.storeCurrentAndFetchNextToken(true, match, stack); // next may be div + while (match.value == '++' || match.value == '--') { + this.failignore('InvalidPostfixOperandObject', match, stack); + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + } + } else if (match.value == 'function') { // function expression + if (this.ast) { //#ifdef FULL_AST + var oldstack = stack; + stack = []; + stack.desc = 'func expr'; + stack.isFunction = true; + stack.nextBlack = match.tokposb; + if (!this.scope.functions) this.scope.functions = []; + match.functionId = this.scope.functions.length; + this.scope.functions.push(match); + oldstack.push(stack); + var oldscope = this.scope; + // add new scope + match.scope = stack.scope = this.scope = [ + this.scope, + {value:'this', isDeclared:true, isEcma:true, functionStack: stack}, + {value:'arguments', isDeclared:true, isEcma:true, varType:['Object']} + ]; // add the current scope (to build chain up-down) + this.scope.upper = oldscope; + // ref to back to function that's the cause for this scope + this.scope.scopeFor = match; + match.targetScope = oldscope; // consistency + match.isFuncExprKeyword = true; + match.functionStack = stack; + } //#endif + var funcExprToken = match; + + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (mayParseLabeledStatementInstead && match.value == ':') match = this.failsafe('LabelsMayNotBeReserved', match); + if (match.name == 2/*IDENTIFIER*/) { + funcExprToken.funcName = match; + match.meta = "func expr name"; + match.varType = ['Function']; + match.functionStack = stack; // ref to the stack, in case we detect the var being a constructor + if (this.ast) { //#ifdef FULL_AST + // name is only available to inner scope + this.scope.push({value:match.value}); + } //#endif + if (this.hashStartKeyOrReserved[match.value[0]] /*this.regexStartKeyOrReserved.test(match.value[0])*/ && this.regexIsKeywordOrReserved.test(match.value)) match = this.failsafe('FunctionNameMustNotBeReserved', match); + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + } + match = this.eatFunctionParametersAndBody(match, stack, true, funcExprToken); // first token after func-expr is div + + while (match.value == '++' || match.value == '--') { + this.failignore('InvalidPostfixOperandFunction', match, stack); + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + } + + if (this.ast) { //#ifdef FULL_AST + // restore stack and scope + stack = oldstack; + this.scope = oldscope; + } //#endif + } else if (match.name <= 6) { // IDENTIFIER STRING_SINGLE STRING_DOUBLE NUMERIC_HEX NUMERIC_DEC REG_EX + // save it in case it turns out to be a label. + var possibleLabel = match; + + // validate the identifier, if any + if (match.name == 2/*IDENTIFIER*/) { + if ( + // this, null, true, false are actually allowed here + !this.regexLiteralKeywords.test(match.value) && + // other reserved words are not + this.hashStartKeyOrReserved[match.value[0]] /*this.regexStartKeyOrReserved.test(match.value[0])*/ && this.regexIsKeywordOrReserved.test(match.value) + ) { + // if break/continue, we skipped the unary operator check so throw the proper error here + if (isBreakOrContinueArg) { + this.failignore('BreakOrContinueArgMustBeJustIdentifier', match, stack); + } else if (match.value == 'else') { + this.failignore('DidNotExpectElseHere', match, stack); + } else { + //if (mayParseLabeledStatementInstead) {new ZeParser.Error('LabelsMayNotBeReserved', match); + // TOFIX: lookahead to see if colon is following. throw label error instead if that's the case + // any forbidden keyword at this point is likely to be a statement start. + // its likely that the parser will take a while to recover from this point... + this.failignore('UnexpectedToken', match, stack); + // TOFIX: maybe i should just return at this point. cut my losses and hope for the best. + } + } + + // only accept assignments after a member expression (identifier or ending with a [] suffix) + acceptAssignment = true; + } else if (isBreakOrContinueArg) match = this.failsafe('BreakOrContinueArgMustBeJustIdentifier', match); + + // the current match is the lead value being queried. tag it that way + if (this.ast) { //#ifdef FULL_AST + // dont mark labels + if (!isBreakOrContinueArg) { + match.meta = 'lead value'; + match.leadValue = true; + } + } //#endif + + + // ok. gobble it. + match = this.tokenizer.storeCurrentAndFetchNextToken(true, match, stack); // division allowed + + // now check for labeled statement (if mayParseLabeledStatementInstead then the first token for this expression must be an (unreserved) identifier) + if (mayParseLabeledStatementInstead && match.value == ':') { + if (possibleLabel.name != 2/*IDENTIFIER*/) { + // label was not an identifier + // TOFIX: this colon might be a different type of error... more analysis required + this.failignore('LabelsMayOnlyBeIdentifiers', match, stack); + } + + mayParseLabeledStatementInstead = true; // mark label parsed (TOFIX:speed?) + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + + possibleLabel.isLabel = true; + if (this.ast) { //#ifdef FULL_AST + delete possibleLabel.meta; // oh oops, it's not a lead value. + + possibleLabel.isLabelDeclaration = true; + this.statementLabels.push(possibleLabel.value); + + stack.desc = 'labeled statement'; + } //#endif + + var errorIdToReplace = this.errorStack.length; + // eat another statement now, its the body of the labeled statement (like if and while) + match = this.eatStatement(false, match, stack); + + // if no statement was found, check here now and correct error + if (match.error && match.error.msg == ZeParser.Errors.UnableToParseStatement.msg) { + // replace with better error... + match.error = new ZeParser.Error('LabelRequiresStatement'); + // also replace on stack + this.errorStack[errorIdToReplace] = match.error; + } + + match.wasLabel = true; + + return match; + } + + mayParseLabeledStatementInstead = false; + } else if (match.value == '}') { + // ignore... its certainly the end of this expression, but maybe asi can be applied... + // it might also be an object literal expecting more, but that case has been covered else where. + // if it turns out the } is bad after all, .parse() will try to recover + } else if (match.name == 14/*error*/) { + do { + if (match.tokenError) { + var pe = new ZeParser.Error('TokenizerError', match); + pe.msg += ': '+match.error.msg; + this.errorStack.push(pe); + + this.failSpecial({start:match.start,stop:match.start,name:14/*error*/,error:pe}, match, stack) + } + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + } while (match.name == 14/*error*/); + } else if (match.name == 12/*eof*/) { + // cant parse any further. you're probably just typing... + return match; + } else { + //if (!this.errorStack.length && match.name != 12/*eof*/) console.log(["unknown token", match, stack, Gui.escape(this.input)]); + this.failignore('UnknownToken', match, stack); + // we cant really ignore this. eat the token and try again. possibly you're just typing? + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + } + + // search for "value" suffix. property access and call parens. + while (match.value == '.' || match.value == '[' || match.value == '(') { + if (isBreakOrContinueArg) match = this.failsafe('BreakOrContinueArgMustBeJustIdentifier', match); + + if (match.value == '.') { + // property access. read in an IdentifierName (no keyword checks). allow assignments + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.name != 2/*IDENTIFIER*/) this.failignore('PropertyNamesMayOnlyBeIdentifiers', match, stack); + if (this.ast) { //#ifdef FULL_AST + match.isPropertyName = true; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(true, match, stack); // may parse div + acceptAssignment = true; + } else if (match.value == '[') { + if (this.ast) { //#ifdef FULL_AST + var lhsb = match; + match.propertyAccessStart = true; + } //#endif + // property access, read expression list. allow assignments + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (!(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value))) { + if (match.value == ']') match = this.failsafe('SquareBracketsMayNotBeEmpty', match); + else match = this.failsafe('SquareBracketExpectsExpression', match); + } + match = this.eatExpressions(false, match, stack); + if (match.value != ']') match = this.failsafe('UnclosedSquareBrackets', match); + if (this.ast) { //#ifdef FULL_AST + match.twin = lhsb; + match.propertyAccessStop = true; + lhsb.twin = match; + + if (stack[stack.length-1].desc == 'expressions') { + // create ref to this expression group to the opening bracket + lhsb.expressionArg = stack[stack.length-1]; + } + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(true, match, stack); // might be div + acceptAssignment = true; + } else if (match.value == '(') { + if (this.ast) { //#ifdef FULL_AST + var lhp = match; + match.isCallExpressionStart = true; + if (news) { + match.parensBelongToNew = true; + --news; + } + } //#endif + // call expression, eat optional expression list, disallow assignments + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value)) match = this.eatExpressions(false, match, stack); // arguments are optional + if (match.value != ')') match = this.failsafe('UnclosedCallParens', match); + if (this.ast) { //#ifdef FULL_AST + match.twin = lhp; + lhp.twin = match; + match.isCallExpressionStop = true; + + if (stack[stack.length-1].desc == 'expressions') { + // create ref to this expression group to the opening bracket + lhp.expressionArg = stack[stack.length-1]; + } + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(true, match, stack); // might be div + acceptAssignment = false; + } + } + + // check for postfix operators ++ and -- + // they are stronger than the + or - binary operators + // they can be applied to any lhs (even when it wouldnt make sense) + // if there was a newline, it should get an ASI + if ((match.value == '++' || match.value == '--') && !match.newline) { + if (isBreakOrContinueArg) match = this.failsafe('BreakOrContinueArgMustBeJustIdentifier', match); + match = this.tokenizer.storeCurrentAndFetchNextToken(true, match, stack); // may parse div + } + + if (this.ast) { //#ifdef FULL_AST + // restore "expression" stack + stack = estack; + } //#endif + // now see if there is an operator following... + + do { // this do allows us to parse multiple ternary expressions in succession without screwing up. + var ternary = false; + if ( + (!forHeader && match.value == 'in') || // one of two named binary operators, may not be first expression in for-header (when semi's occur in the for-header) + (match.value == 'instanceof') || // only other named binary operator + ((match.name == 11/*PUNCTUATOR*/) && // we can only expect a punctuator now + (match.isAssignment = this.regexAssignments.test(match.value)) || // assignments are only okay with proper lhs + this.regexNonAssignmentBinaryExpressionOperators.test(match.value) // test all other binary operators + ) + ) { + if (match.isAssignment) { + if (!acceptAssignment) this.failignore('IllegalLhsForAssignment', match, stack); + else if (parsedNonAssignmentOperator) this.failignore('AssignmentNotAllowedAfterNonAssignmentInExpression', match, stack); + } + if (isBreakOrContinueArg) match = this.failsafe('BreakOrContinueArgMustBeJustIdentifier', match); + + if (!match.isAssignment) parsedNonAssignmentOperator = true; // last allowed assignment + if (this.ast) { //#ifdef FULL_AST + match.isBinaryOperator = true; + // we build a stack to ensure any whitespace doesnt break the 1+(n*2) children rule for expressions + var ostack = stack; + stack = []; + stack.desc = 'operator-expression'; + stack.isBinaryOperator = true; + stack.sub = match.value; + stack.nextBlack = match.tokposb; + ostack.sub = match.value; + stack.isAssignment = match.isAssignment; + ostack.push(stack); + } //#endif + ternary = match.value == '?'; + // math, logic, assignment or in or instanceof + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + + if (this.ast) { //#ifdef FULL_AST + // restore "expression" stack + stack = ostack; + } //#endif + + // minor exception to ternary operator, we need to parse two expressions nao. leave the trailing expression to the loop. + if (ternary) { + // LogicalORExpression "?" AssignmentExpression ":" AssignmentExpression + // so that means just one expression center and right. + if (!(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value))) this.failignore('InvalidCenterTernaryExpression', match, stack); + match = this.eatExpressions(false, match, stack, true, forHeader); // only one expression allowed inside ternary center/right + + if (match.value != ':') { + if (match.value == ',') match = this.failsafe('TernarySecondExpressionCanNotContainComma', match); + else match = this.failsafe('UnfinishedTernaryOperator', match); + } + if (this.ast) { //#ifdef FULL_AST + // we build a stack to ensure any whitespace doesnt break the 1+(n*2) children rule for expressions + var ostack = stack; + stack = []; + stack.desc = 'operator-expression'; + stack.sub = match.value; + stack.nextBlack = match.tokposb; + ostack.sub = match.value; + stack.isAssignment = match.isAssignment; + ostack.push(stack); + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (this.ast) { //#ifdef FULL_AST + stack = ostack; + } //#endif + // rhs of the ternary can not contain a comma either + match = this.eatExpressions(false, match, stack, true, forHeader); // only one expression allowed inside ternary center/right + } + } else { + parseAnotherExpression = false; + } + } while (ternary); // if we just parsed a ternary expression, we need to check _again_ whether the next token is a binary operator. + + // start over. match is the rhs for the lhs we just parsed, but lhs for the next expression + if (parseAnotherExpression && !(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value))) { + // no idea what to do now. lets just ignore and see where it ends. TOFIX: maybe just break the loop or return? + this.failignore('InvalidRhsExpression', match, stack); + } + } + + if (this.ast) { //#ifdef FULL_AST + // restore "expressions" stack + stack = astack; + } //#endif + + // at this point we should have parsed one AssignmentExpression + // lets see if we can parse another one... + mayParseLabeledStatementInstead = first = false; + } while (!onlyOne && match.value == ','); + + if (this.ast) { //#ifdef FULL_AST + // remove empty array + if (!stack.length) pstack.length = pstack.length-1; + pstack.numberOfExpressions = parsedExpressions; + if (pstack[0]) pstack[0].numberOfExpressions = parsedExpressions; + stack.expressionCount = parsedExpressions; + } //#endif + return match; + }, + eatFunctionDeclaration: function(match, stack){ + if (this.ast) { //#ifdef FULL_AST + stack.push(stack = []); + var prevscope = this.scope; + stack.desc = 'func decl'; + stack.isFunction = true; + stack.nextBlack = match.tokposb; + if (!this.scope.functions) this.scope.functions = []; + match.functionId = this.scope.functions.length; + this.scope.functions.push(match); + // add new scope + match.scope = stack.scope = this.scope = [ + this.scope, // add current scope (build scope chain up-down) + // Object.create(null, + {value:'this', isDeclared:true, isEcma:true, functionStack:stack}, + // Object.create(null, + {value:'arguments', isDeclared:true, isEcma:true, varType:['Object']} + ]; + // ref to back to function that's the cause for this scope + this.scope.scopeFor = match; + match.targetScope = prevscope; // consistency + + match.functionStack = stack; + + match.isFuncDeclKeyword = true; + } //#endif + // only place that this function is used already checks whether next token is function + var functionKeyword = match; + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.name != 2/*IDENTIFIER*/) match = this.failsafe('FunctionDeclarationsMustHaveName', match); + if (this.hashStartKeyOrReserved[match.value[0]] /*this.regexStartKeyOrReserved.test(match.value[0])*/ && this.regexIsKeywordOrReserved.test(match.value)) this.failignore('FunctionNameMayNotBeReserved', match, stack); + if (this.ast) { //#ifdef FULL_AST + functionKeyword.funcName = match; + prevscope.push({value:match.value}); + match.meta = 'func decl name'; // that's what it is, really + match.varType = ['Function']; + match.functionStack = stack; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + match = this.eatFunctionParametersAndBody(match, stack, false, functionKeyword); // first token after func-decl is regex + if (this.ast) { //#ifdef FULL_AST + // restore previous scope + this.scope = prevscope; + } //#endif + return match; + }, + eatObjectLiteralColonAndBody: function(match, stack){ + if (this.ast) { //#ifdef FULL_AST + var propValueStack = stack; + stack = []; + stack.desc = 'objlit pair colon'; + stack.nextBlack = match.tokposb; + propValueStack.push(stack); + } //#endif + if (match.value != ':') match = this.failsafe('ObjectLiteralExpectsColonAfterName', match); + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (this.ast) { //#ifdef FULL_AST + stack = propValueStack; + } //#endif + + // this might actually fail due to ASI optimization. + // if the property name does not exist and it is the last item + // of the objlit, the expression parser will see an unexpected + // } and ignore it, giving some leeway to apply ASI. of course, + // that doesnt work for objlits. but we dont want to break the + // existing mechanisms. so we check this differently... :) + var prevMatch = match; + match = this.eatExpressions(false, match, stack, true); // only one expression + if (match == prevMatch) match = this.failsafe('ObjectLiteralMissingPropertyValue', match); + + return match; + }, + eatFunctionParametersAndBody: function(match, stack, div, funcToken){ + // div: the first token _after_ a function expression may be a division... + if (match.value != '(') match = this.failsafe('ExpectingFunctionHeaderStart', match); + else if (this.ast) { //#ifdef FULL_AST + var lhp = match; + funcToken.lhp = match; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.name == 2/*IDENTIFIER*/) { // params + if (this.hashStartKeyOrReserved[match.value[0]] /*this.regexStartKeyOrReserved.test(match.value[0])*/ && this.regexIsKeywordOrReserved.test(match.value)) this.failignore('FunctionArgumentsCanNotBeReserved', match, stack); + if (this.ast) { //#ifdef FULL_AST + if (!funcToken.paramNames) funcToken.paramNames = []; + stack.paramNames = funcToken.paramNames; + funcToken.paramNames.push(match); + this.scope.push({value:match.value}); // add param name to scope + match.meta = 'parameter'; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + while (match.value == ',') { + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.name != 2/*IDENTIFIER*/) { + // example: if name is 12, the source is incomplete... + this.failignore('FunctionParametersMustBeIdentifiers', match, stack); + } else if (this.hashStartKeyOrReserved[match.value[0]] /*this.regexStartKeyOrReserved.test(match.value[0])*/ && this.regexIsKeywordOrReserved.test(match.value)) { + this.failignore('FunctionArgumentsCanNotBeReserved', match, stack); + } + if (this.ast) { //#ifdef FULL_AST + // Object.create(null, + this.scope.push({value:match.value}); // add param name to scope + match.meta = 'parameter'; + if (match.name == 2/*IDENTIFIER*/) funcToken.paramNames.push(match); + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + } + } + if (this.ast) { //#ifdef FULL_AST + if (lhp) { + match.twin = lhp; + lhp.twin = match; + funcToken.rhp = match; + } + } //#endif + if (match.value != ')') match = this.failsafe('ExpectedFunctionHeaderClose', match); // TOFIX: can be various things here... + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + match = this.eatFunctionBody(match, stack, div, funcToken); + return match; + }, + eatFunctionBody: function(match, stack, div, funcToken){ + if (this.ast) { //#ifdef FULL_AST + stack.push(stack = []); + stack.desc = 'func body'; + stack.nextBlack = match.tokposb; + + // create EMPTY list of functions. labels cannot cross function boundaries + var labelBackup = this.statementLabels; + this.statementLabels = []; + stack.labels = this.statementLabels; + } //#endif + + // if div, a division can occur _after_ this function expression + //this.stats.eatFunctionBody = (+//this.stats.eatFunctionBody||0)+1; + if (match.value != '{') match = this.failsafe('ExpectedFunctionBodyCurlyOpen', match); + if (this.ast) { //#ifdef FULL_AST + var lhc = match; + if (funcToken) funcToken.lhc = lhc; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + match = this.eatSourceElements(match, stack); + if (match.value != '}') match = this.failsafe('ExpectedFunctionBodyCurlyClose', match); + if (this.ast) { //#ifdef FULL_AST + match.twin = lhc; + lhc.twin = match; + if (funcToken) funcToken.rhc = match; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(div, match, stack); + + if (this.ast) { //#ifdef FULL_AST + // restore label set + this.statementLabels = labelBackup; + } //#endif + + return match; + }, + eatVar: function(match, stack){ + if (this.ast) { //#ifdef FULL_AST + stack.push(stack = []); + stack.desc = 'statement'; + stack.sub = 'var'; + stack.nextBlack = match.tokposb; + match.stack = stack; + match.isVarKeyword = true; + } //#endif + match = this.eatVarDecl(match, stack); + match = this.eatSemiColon(match, stack); + + return match; + }, + eatVarDecl: function(match, stack, forHeader){ + // assumes match is indeed the identifier 'var' + if (this.ast) { //#ifdef FULL_AST + stack.push(stack = []); + stack.desc = 'var decl'; + stack.nextBlack = match.tokposb; + + var targetScope = this.scope; + while (targetScope.catchScope) targetScope = targetScope[0]; + } //#endif + var first = true; + var varsDeclared = 0; + do { + ++varsDeclared; + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); // start: var, iteration: comma + if (this.ast) { //#ifdef FULL_AST + var declStack = stack; + var stack = []; + stack.desc = 'single var decl'; + stack.varStack = declStack; // reference to the var statement stack, it might hook to jsdoc needed for these vars + stack.nextBlack = match.tokposb; + declStack.push(stack); + + var singleDecStack = stack; + stack = []; + stack.desc = 'sub-expression'; + stack.nextBlack = match.tokposb; + singleDecStack.push(stack); + } //#endif + + // next token should be a valid identifier + if (match.name == 12/*eof*/) { + if (first) match = this.failsafe('VarKeywordMissingName', match); + // else, ignore. TOFIX: return? + else match = this.failsafe('IllegalTrailingComma', match); + } else if (match.name != 2/*IDENTIFIER*/) { + match = this.failsafe('VarNamesMayOnlyBeIdentifiers', match); + } else if (this.hashStartKeyOrReserved[match.value[0]] /*this.regexStartKeyOrReserved.test(match.value[0])*/ && this.regexIsKeywordOrReserved.test(match.value)) { + match = this.failsafe('VarNamesCanNotBeReserved', match); + } + // mark the match as being a variable name. we need it for lookup later :) + if (this.ast) { //#ifdef FULL_AST + match.meta = 'var name'; + targetScope.push({value:match.value}); + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + + if (this.ast) { //#ifdef FULL_AST + stack = singleDecStack; + } //#endif + + // next token should either be a = , or ; + // if = parse an expression and optionally a comma + if (match.value == '=') { + if (this.ast) { //#ifdef FULL_AST + singleDecStack = stack; + stack = []; + stack.desc = 'operator-expression'; + stack.sub = '='; + stack.nextBlack = match.tokposb; + singleDecStack.push(stack); + + stack.isAssignment = true; + } //#endif + match.isInitialiser = true; + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (this.ast) { //#ifdef FULL_AST + stack = singleDecStack; + } //#endif + + if (!(/*is left hand side start?*/ match.name <= 6 || match.name == 14/*error*/ || this.regexLhsStart.test(match.value))) match = this.failsafe('VarInitialiserExpressionExpected', match); + match = this.eatExpressions(false, match, stack, true, forHeader); // only one expression + // var statement: comma or semi now + // for statement: semi, comma or 'in' + } + if (this.ast) { //#ifdef FULL_AST + stack = declStack; + } //#endif + + // determines proper error message in one case + first = false; + // keep parsing name(=expression) sequences as long as you see a comma here + } while (match.value == ','); + + if (this.ast) { //#ifdef FULL_AST + stack.varsDeclared = varsDeclared; + } //#endif + + return match; + }, + + eatIf: function(match, stack){ + if (this.ast) { //#ifdef FULL_AST + stack.push(stack = []); + stack.desc = 'statement'; + stack.sub = 'if'; + stack.hasElse = false; + stack.nextBlack = match.tokposb; + } //#endif + // ( + // expression + // ) + // statement + // [else statement] + var ifKeyword = match; + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.value != '(') match = this.failsafe('ExpectedStatementHeaderOpen', match); + if (this.ast) { //#ifdef FULL_AST + var lhp = match; + match.statementHeaderStart = true; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (!(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value))) match = this.failsafe('StatementHeaderIsNotOptional', match); + match = this.eatExpressions(false, match, stack); + if (match.value != ')') match = this.failsafe('ExpectedStatementHeaderClose', match); + if (this.ast) { //#ifdef FULL_AST + match.twin = lhp; + match.statementHeaderStop = true; + lhp.twin = match; + + if (stack[stack.length-1].desc == 'expressions') { + // create ref to this expression group to the opening bracket + lhp.expressionArg = stack[stack.length-1]; + } + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + match = this.eatStatement(false, match, stack); + + // match might be null here... (if the if-statement was end part of the source) + if (match && match.value == 'else') { + if (this.ast) { //#ifdef FULL_AST + ifKeyword.hasElse = match; + } //#endif + match = this.eatElse(match, stack); + } + + return match; + }, + eatElse: function(match, stack){ + if (this.ast) { //#ifdef FULL_AST + stack.hasElse = true; + stack.push(stack = []); + stack.desc = 'statement'; + stack.sub = 'else'; + stack.nextBlack = match.tokposb; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + match = this.eatStatement(false, match, stack); + + return match; + }, + eatDo: function(match, stack){ + if (this.ast) { //#ifdef FULL_AST + stack.push(stack = []); + stack.desc = 'statement'; + stack.sub = 'do'; + stack.isIteration = true; + stack.nextBlack = match.tokposb; + this.statementLabels.push(''); // add "empty" + var doToken = match; + } //#endif + // statement + // while + // ( + // expression + // ) + // semi-colon + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + match = this.eatStatement(false, match, stack); + if (match.value != 'while') match = this.failsafe('DoShouldBeFollowedByWhile', match); + if (this.ast) { //#ifdef FULL_AST + match.hasDo = doToken; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.value != '(') match = this.failsafe('ExpectedStatementHeaderOpen', match); + if (this.ast) { //#ifdef FULL_AST + var lhp = match; + match.statementHeaderStart = true; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (!(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value))) match = this.failsafe('StatementHeaderIsNotOptional', match); + match = this.eatExpressions(false, match, stack); + if (match.value != ')') match = this.failsafe('ExpectedStatementHeaderClose', match); + if (this.ast) { //#ifdef FULL_AST + match.twin = lhp; + match.statementHeaderStop = true; + match.isForDoWhile = true; // prevents missing block warnings + lhp.twin = match; + + if (stack[stack.length-1].desc == 'expressions') { + // create ref to this expression group to the opening bracket + lhp.expressionArg = stack[stack.length-1]; + } + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + match = this.eatSemiColon(match, stack); // TOFIX: this is not optional according to the spec, but browsers apply ASI anyways + + return match; + }, + eatWhile: function(match, stack){ + if (this.ast) { //#ifdef FULL_AST + stack.push(stack = []); + stack.desc = 'statement'; + stack.sub = 'while'; + stack.isIteration = true; + stack.nextBlack = match.tokposb; + this.statementLabels.push(''); // add "empty" + } //#endif + + // ( + // expression + // ) + // statement + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.value != '(') match = this.failsafe('ExpectedStatementHeaderOpen', match); + if (this.ast) { //#ifdef FULL_AST + var lhp = match; + match.statementHeaderStart = true; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (!(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value))) match = this.failsafe('StatementHeaderIsNotOptional', match); + match = this.eatExpressions(false, match, stack); + if (match.value != ')') match = this.failsafe('ExpectedStatementHeaderClose', match); + if (this.ast) { //#ifdef FULL_AST + match.twin = lhp; + match.statementHeaderStop = true; + lhp.twin = match; + + if (stack[stack.length-1].desc == 'expressions') { + // create ref to this expression group to the opening bracket + lhp.expressionArg = stack[stack.length-1]; + } + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + match = this.eatStatement(false, match, stack); + + return match; + }, + + eatFor: function(match, stack){ + if (this.ast) { //#ifdef FULL_AST + stack.push(stack = []); + stack.desc = 'statement'; + stack.sub = 'for'; + stack.isIteration = true; + stack.nextBlack = match.tokposb; + this.statementLabels.push(''); // add "empty" + } //#endif + // either a for(..in..) or for(..;..;..) + // start eating an expression but refuse to parse + // 'in' on the top-level of that expression. they are fine + // in sub-levels (group, array, etc). Now the expression + // must be followed by either ';' or 'in'. Else throw. + // Branch on that case, ; requires two. + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.value != '(') match = this.failsafe('ExpectedStatementHeaderOpen', match); + if (this.ast) { //#ifdef FULL_AST + var lhp = match; + match.statementHeaderStart = true; + match.forHeaderStart = true; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + + // for (either case) may start with var, in which case you'll parse a var declaration before encountering the 'in' or first semi. + if (match.value == 'var') { + match = this.eatVarDecl(match, stack, true); + } else if (match.value != ';') { // expressions are optional in for-each + if (!(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value))) { + this.failignore('StatementHeaderIsNotOptional', match, stack); + } + match = this.eatExpressions(false, match, stack, false, true); // can parse multiple expressions, in is not ok here + } + + // now we parsed an expression if it existed. the next token should be either ';' or 'in'. branch accordingly + if (match.value == 'in') { + var declStack = stack[stack.length-1]; + if (declStack.varsDeclared > 1) { + // disallowed. for-in var decls can only have one var name declared + this.failignore('ForInCanOnlyDeclareOnVar', match, stack); + } + + if (this.ast) { //#ifdef FULL_AST + stack.forType = 'in'; + match.forFor = true; // make easy distinction between conditional and iterational operator + } //#endif + + // just parse another expression, where 'in' is allowed. + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + match = this.eatExpressions(false, match, stack); + } else { + if (match.value != ';') match = this.failsafe('ForHeaderShouldHaveSemisOrIn', match); + + if (this.ast) { //#ifdef FULL_AST + stack.forType = 'each'; + match.forEachHeaderStart = true; + } //#endif + // parse another optional no-in expression, another semi and then one more optional no-in expression + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value)) match = this.eatExpressions(false, match, stack); // in is ok here + if (match.value != ';') match = this.failsafe('ExpectedSecondSemiOfForHeader', match); + if (this.ast) { //#ifdef FULL_AST + match.forEachHeaderStop = true; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value)) match = this.eatExpressions(false, match, stack); // in is ok here + } + + if (match.value != ')') match = this.failsafe('ExpectedStatementHeaderClose', match); + if (this.ast) { //#ifdef FULL_AST + match.twin = lhp; + match.statementHeaderStop = true; + match.forHeaderStop = true; + lhp.twin = match; + + if (match.forType == 'in' && stack[stack.length-1].desc == 'expressions') { + // create ref to this expression group to the opening bracket + lhp.expressionArg = stack[stack.length-1]; + } + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + + match = this.eatStatement(false, match, stack); + + return match; + }, + eatContinue: function(match, stack){ + if (this.ast) { //#ifdef FULL_AST + stack.push(stack = []); + stack.desc = 'statement'; + stack.sub = 'continue'; + stack.nextBlack = match.tokposb; + + match.restricted = true; + } //#endif + // (no-line-break identifier) + // ; + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); // may not have line terminator... + if (!match.newline && match.value != ';' && match.name != 12/*EOF*/ && match.value != '}') { + if (this.ast) { //#ifdef FULL_AST + match.isLabel = true; + match.isLabelTarget = true; + + var continueArg = match; // remember to see if this continue parsed a label + } //#endif + // may only parse exactly an identifier at this point + match = this.eatExpressions(false, match, stack, true, false, true); // first true=onlyOne, second: continue/break arg + if (this.ast) { //#ifdef FULL_AST + stack.hasLabel = continueArg != match; + } //#endif + if (match.value != ';' && !match.newline && match.name != 12/*eof*/ && match.value != '}') match = this.failsafe('BreakOrContinueArgMustBeJustIdentifier', match); + } + match = this.eatSemiColon(match, stack); + + return match; + }, + eatBreak: function(match, stack){ + if (this.ast) { //#ifdef FULL_AST + var parentstack = stack + stack = []; + stack.desc = 'statement'; + stack.sub = 'break'; + stack.nextBlack = match.tokposb; + + parentstack.push(stack); + + match.restricted = true; + } //#endif + // (no-line-break identifier) + // ; + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); // may not have line terminator... + if (!match.newline && match.value != ';' && match.name != 12/*EOF*/ && match.value != '}') { + if (this.ast) { //#ifdef FULL_AST + match.isLabel = true; + match.isLabelTarget = true; + var breakArg = match; // remember to see if this break parsed a label + } //#endif + // may only parse exactly an identifier at this point + match = this.eatExpressions(false, match, stack, true, false, true); // first true=onlyOne, second: continue/break arg + if (this.ast) { //#ifdef FULL_AST + stack.hasLabel = breakArg != match; + } //#endif + + if (match.value != ';' && !match.newline && match.name != 12/*eof*/ && match.value != '}') match = this.failsafe('BreakOrContinueArgMustBeJustIdentifier', match); + } + match = this.eatSemiColon(match, stack); + + return match; + }, + eatReturn: function(match, stack){ + if (this.ast) { //#ifdef FULL_AST + stack.push(stack = []); + stack.desc = 'statement'; + stack.sub = 'return'; + stack.nextBlack = match.tokposb; + stack.returnFor = this.scope.scopeFor; + + match.restricted = true; + } //#endif + // (no-line-break expression) + // ; + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); // may not have line terminator... + if (!match.newline && match.value != ';' && match.name != 12/*EOF*/ && match.value != '}') { + match = this.eatExpressions(false, match, stack); + } + match = this.eatSemiColon(match, stack); + + return match; + }, + eatThrow: function(match, stack){ + if (this.ast) { //#ifdef FULL_AST + stack.push(stack = []); + stack.desc = 'statement'; + stack.sub = 'throw'; + stack.nextBlack = match.tokposb; + + match.restricted = true; + } //#endif + // (no-line-break expression) + // ; + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); // may not have line terminator... + if (match.newline) match = this.failsafe('ThrowCannotHaveReturn', match); + if (match.value == ';') match = this.failsafe('ThrowMustHaveArgument', match); + match = this.eatExpressions(false, match, stack); + match = this.eatSemiColon(match, stack); + + return match; + }, + eatSwitch: function(match, stack){ + if (this.ast) { //#ifdef FULL_AST + stack.push(stack = []); + stack.desc = 'statement'; + stack.sub = 'switch'; + stack.nextBlack = match.tokposb; + + this.statementLabels.push(''); // add "empty" + } //#endif + // meh. + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.value != '(') match = this.failsafe('ExpectedStatementHeaderOpen', match); + if (this.ast) { //#ifdef FULL_AST + var lhp = match; + match.statementHeaderStart = true; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (!(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value))) { + this.failignore('StatementHeaderIsNotOptional', match, stack); + } + match = this.eatExpressions(false, match, stack); + if (match.value != ')') match = this.failsafe('ExpectedStatementHeaderClose', match); + if (this.ast) { //#ifdef FULL_AST + match.twin = lhp; + match.statementHeaderStop = true; + lhp.twin = match; + + if (stack[stack.length-1].desc == 'expressions') { + // create ref to this expression group to the opening bracket + lhp.expressionArg = stack[stack.length-1]; + } + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.value != '{') match = this.failsafe('SwitchBodyStartsWithCurly', match); + + if (this.ast) { //#ifdef FULL_AST + var lhc = match; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + + // you may parse a default case, and only once per switch. but you may do so anywhere. + var parsedAnything = false; + + while (match.value == 'case' || (!stack.parsedSwitchDefault && match.value == 'default')) { + parsedAnything = true; + if (match.value == 'default') stack.parsedSwitchDefault = true; + + match = this.eatSwitchClause(match, stack); + } + + // if you didnt parse anything but not encountering a closing curly now, you might be thinking that switches may start with silly stuff + if (!parsedAnything && match.value != '}') { + match = this.failsafe('SwitchBodyMustStartWithClause', match); + } + + if (stack.parsedSwitchDefault && match.value == 'default') { + this.failignore('SwitchCannotHaveDoubleDefault', match, stack); + } + + if (match.value != '}' && match.name != 14/*error*/) match = this.failsafe('SwitchBodyEndsWithCurly', match); + + if (this.ast) { //#ifdef FULL_AST + match.twin = lhc; + lhc.twin = match; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + + return match; + }, + eatSwitchClause: function(match, stack){ + match = this.eatSwitchHeader(match, stack); + match = this.eatSwitchBody(match, stack); + + return match; + }, + eatSwitchHeader: function(match, stack){ + if (this.ast) { //#ifdef FULL_AST + // collect whitespace... + var switchHeaderStack = stack + stack.push(stack = []); + stack.desc = 'switch clause header'; + stack.nextBlack = match.tokposb; + } //#endif + + if (match.value == 'case') { + match = this.eatSwitchCaseHead(match, stack); + } else { // default + if (this.ast) { //#ifdef FULL_AST + switchHeaderStack.hasDefaultClause = true; + } //#endif + match = this.eatSwitchDefaultHead(match, stack); + } + + if (this.ast) { //#ifdef FULL_AST + // just to group whitespace (makes certain navigation easier..) + stack.push(stack = []); + stack.desc = 'colon'; + stack.nextBlack = match.tokposb; + } //#endif + + if (match.value != ':') { + match = this.failsafe('SwitchClausesEndWithColon', match); + } + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + + return match; + }, + eatSwitchBody: function(match, stack){ + if (this.ast) { //#ifdef FULL_AST + stack.push(stack = []); + stack.desc = 'switch clause body'; + stack.nextBlack = match.tokposb; + } //#endif + + // parse body of case or default, just so long case and default keywords are not seen and end of switch is not reached + // (clause bodies may be empty, for instance to fall through) + var lastMatch = null; + while (match.value != 'default' && match.value != 'case' && match.value != '}' && match.name != 14/*error*/ && match.name != 12/*eof*/ && lastMatch != match) { + lastMatch = match; // prevents endless loops on error ;) + match = this.eatStatement(true, match, stack); + } + if (lastMatch == match) this.failsafe('UnexpectedInputSwitch', match); + + return match; + }, + eatSwitchCaseHead: function(match, stack){ + if (this.ast) { //#ifdef FULL_AST + stack.sub = 'case'; + var caseHeadStack = stack; + + stack.push(stack = []); + stack.desc = 'case'; + stack.nextBlack = match.tokposb; + + match.isCase = true; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + + if (match.value == ':') { + this.failignore('CaseMissingExpression', match, stack); + } else { + if (this.ast) { //#ifdef FULL_AST + caseHeadStack.push(stack = []); + stack.desc = 'case arg'; + stack.nextBlack = match.tokposb; + } //#endif + match = this.eatExpressions(false, match, stack); + } + + return match; + }, + eatSwitchDefaultHead: function(match, stack){ + if (this.ast) { //#ifdef FULL_AST + stack.sub = 'default'; + + stack.push(stack = []); + stack.desc = 'case'; + stack.nextBlack = match.tokposb; + + match.isDefault = true; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + + return match; + }, + eatTryCatchFinally: function(match, stack){ + if (this.ast) { //#ifdef FULL_AST + stack.push(stack = []); + stack.desc = 'statement'; + stack.sub = 'try'; + stack.nextBlack = match.tokposb; + } //#endif + + match = this.eatTry(match, stack); + + if (match.value == 'catch') { + if (this.ast) { //#ifdef FULL_AST + stack.hasCatch = true; + } //#endif + match = this.eatCatch(match, stack); + } + if (match.value == 'finally') { + if (this.ast) { //#ifdef FULL_AST + stack.hasFinally = true; + } //#endif + match = this.eatFinally(match, stack); + } + + // at least a catch or finally block must follow. may be both. + if (!stack.tryHasCatchOrFinally) { + this.failignore('TryMustHaveCatchOrFinally', match, stack); + } + + return match; + }, + eatTry: function(match, stack){ + // block + // (catch ( identifier ) block ) + // (finally block) + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.value != '{') match = this.failsafe('MissingTryBlockCurlyOpen', match); + + if (this.ast) { //#ifdef FULL_AST + stack.push(stack = []); + stack.desc = 'statement'; + stack.sub = 'tryblock'; + stack.nextBlack = match.tokposb; + var lhc = match; + } //#endif + + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.value != '}') match = this.eatStatements(match, stack); + if (match.value != '}') match = this.failsafe('MissingTryBlockCurlyClose', match); + + if (this.ast) { //#ifdef FULL_AST + match.twin = lhc; + lhc.twin = match; + } //#endif + + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + + return match; + }, + eatCatch: function(match, stack){ + stack.tryHasCatchOrFinally = true; + if (this.ast) { //#ifdef FULL_AST + stack.push(stack = []); + stack.desc = 'statement'; + stack.sub = 'catch'; + stack.nextBlack = match.tokposb; + + // the catch block has a header which can contain at most one parameter + // this parameter is bound to a local stack. formally, if that parameter + // shadows another variable, changes made to the variable inside the catch + // should not be reflected by the variable being shadowed. however, this + // is not very safe to rely on so there ought to be a warning. note that + // only this parameter gets bound to this inner scope, other parameters. + + var catchScopeBackup = this.scope; + match.scope = this.scope = stack.scope = [this.scope]; + this.scope.catchScope = true; // mark this as being a catchScope + + // find first function scope or global scope object... + var nonCatchScope = catchScopeBackup; + while (nonCatchScope.catchScope) nonCatchScope = nonCatchScope[0]; + + // get catch id, which is governed by the function/global scope only + if (!nonCatchScope.catches) nonCatchScope.catches = []; + match.catchId = nonCatchScope.catches.length; + nonCatchScope.catches.push(match); + match.targetScope = nonCatchScope; + match.catchScope = this.scope; + + // ref to back to function that's the cause for this scope + this.scope.scopeFor = match; + // catch clauses dont have a special `this` or `arguments`, map them to their parent scope + if (catchScopeBackup.global) this.scope.push(catchScopeBackup[0]); // global (has no `arguments` but always a `this`) + else if (catchScopeBackup.catchScope) { + // tricky. there will at least be a this + this.scope.push(catchScopeBackup[1]); + // but there might not be an arguments + if (catchScopeBackup[2] && catchScopeBackup[2].value == 'arguments') this.scope.push(catchScopeBackup[2]); + } else this.scope.push(catchScopeBackup[1], catchScopeBackup[2]); // function scope, copy this and arguments + } //#endif + + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.value != '(') match = this.failsafe('CatchHeaderMissingOpen', match); + if (this.ast) { //#ifdef FULL_AST + var lhp = match; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.name != 2/*IDENTIFIER*/) match = this.failsafe('MissingCatchParameter', match); + if (this.hashStartKeyOrReserved[match.value[0]] /*this.regexStartKeyOrReserved.test(match.value[0])*/ && this.regexIsKeywordOrReserved.test(match.value)) { + this.failignore('CatchParameterNameMayNotBeReserved', match, stack); + } + + if (this.ast) { //#ifdef FULL_AST + match.meta = 'var name'; + // this is the catch variable. bind it to a scope but keep the scope as + // it currently is. + this.scope.push(match); + match.isCatchVar = true; + } //#endif + + // now the catch body will use the outer scope to bind new variables. the problem is that + // inner scopes, if any, should have access to the scope variable, so their scope should + // be linked to the catch scope. this is a problem in the current architecture but the + // idea is to pass on the catchScope as the scope to the eatStatements call, etc. + + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.value != ')') match = this.failsafe('CatchHeaderMissingClose', match); + if (this.ast) { //#ifdef FULL_AST + match.twin = lhp; + lhp.twin = match; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.value != '{') match = this.failsafe('MissingCatchBlockCurlyOpen', match); + if (this.ast) { //#ifdef FULL_AST + var lhc = match; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + + // catch body. statements are optional. + if (match.value != '}') match = this.eatStatements(match, stack); + + if (match.value != '}') match = this.failsafe('MissingCatchBlockCurlyClose', match); + if (this.ast) { //#ifdef FULL_AST + match.twin = lhc; + lhc.twin = match; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + + if (this.ast) { //#ifdef FULL_AST + this.scope = catchScopeBackup; + } //#endif + + return match; + }, + eatFinally: function(match, stack){ + stack.tryHasCatchOrFinally = true; + if (this.ast) { //#ifdef FULL_AST + stack.push(stack = []); + stack.desc = 'statement'; + stack.sub = 'finally'; + stack.nextBlack = match.tokposb; + } //#endif + + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.value != '{') match = this.failsafe('MissingFinallyBlockCurlyOpen', match); + if (this.ast) { //#ifdef FULL_AST + var lhc = match; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.value != '}') match = this.eatStatements(match, stack); + if (match.value != '}') match = this.failsafe('MissingFinallyBlockCurlyClose', match); + if (this.ast) { //#ifdef FULL_AST + match.twin = lhc; + lhc.twin = match; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + + return match; + }, + eatDebugger: function(match, stack){ + if (this.ast) { //#ifdef FULL_AST + stack.push(stack = []); + stack.desc = 'statement'; + stack.sub = 'debugger'; + stack.nextBlack = match.tokposb; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + match = this.eatSemiColon(match, stack); + + return match; + }, + eatWith: function(match, stack){ + if (this.ast) { //#ifdef FULL_AST + stack.push(stack = []); + stack.desc = 'statement'; + stack.sub = 'with'; + stack.nextBlack = match.tokposb; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (match.value != '(') match = this.failsafe('ExpectedStatementHeaderOpen', match); + if (this.ast) { //#ifdef FULL_AST + var lhp = match; + match.statementHeaderStart = true; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + if (!(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value))) match = this.failsafe('StatementHeaderIsNotOptional', match); + match = this.eatExpressions(false, match, stack); + if (match.value != ')') match = this.failsafe('ExpectedStatementHeaderClose', match); + if (this.ast) { //#ifdef FULL_AST + match.twin = lhp; + match.statementHeaderStop = true; + lhp.twin = match; + + if (stack[stack.length-1].desc == 'expressions') { + // create ref to this expression group to the opening bracket + lhp.expressionArg = stack[stack.length-1]; + } + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + match = this.eatStatement(false, match, stack); + + return match; + }, + eatFunction: function(match, stack){ + var pe = new ZeParser.Error + this.errorStack.push(pe); + // ignore. browsers will accept it anyways + var error = {start:match.stop,stop:match.stop,name:14/*error*/,error:pe}; + this.specialError(error, match, stack); + // now try parsing a function declaration... + match = this.eatFunctionDeclaration(match, stack); + + return match; + }, + eatLabelOrExpression: function(match, stack){ + if (this.ast) { //#ifdef FULL_AST + var parentstack = stack; + + stack = []; + stack.desc = 'statement'; + stack.sub = 'expression'; + stack.nextBlack = match.tokposb; + parentstack.push(stack); + } //#endif + // must be an expression or a labeled statement. + // in order to prevent very weird return constructs, we'll first check the first match + // if that's an identifier, we'll gobble it here and move on to the second. + // if that's a colon, we'll gobble it as a labeled statement. otherwise, we'll pass on + // control to eatExpression, with the note that we've already gobbled a + + match = this.eatExpressions(true, match, stack); + // if we parsed a label, the returned match (colon) will have this property + if (match.wasLabel) { + if (this.ast) { //#ifdef FULL_AST + stack.sub = 'labeled'; + } //#endif + // it will have already eaten another statement for the label + } else { + if (this.ast) { //#ifdef FULL_AST + stack.sub = 'expression'; + } //#endif + // only parse semi if we didnt parse a label just now... + match = this.eatSemiColon(match, stack); + } + + return match; + }, + eatBlock: function(match, stack){ + if (this.ast) { //#ifdef FULL_AST + stack.sub = 'block'; + var lhc = match; + } //#endif + + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + + if (match.value == '}') { + if (this.ast) { //#ifdef FULL_AST + stack.isEmptyBlock = true; + } //#endif + } else { + match = this.eatStatements(match, stack); + } + if (match.value != '}') match = this.failsafe('BlockCurlyClose', match); + if (this.ast) { //#ifdef FULL_AST + match.twin = lhc; + lhc.twin = match; + } //#endif + match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); + + return match; + }, + + eatStatements: function(match, stack){ + //this.stats.eatStatements = (+//this.stats.eatStatements||0)+1; + // detecting the start of a statement "quickly" is virtually impossible. + // instead we keep eating statements until the match stops changing + // the first argument indicates that the statement is optional. if that + // statement was not found, the input match will also be the output. + + while (match != (match = this.eatStatement(true, match, stack))); + return match; + }, + eatStatement: function(isOptional, match, stack){ + if (!match && isOptional) return match; // eof + + if (this.ast) { //#ifdef FULL_AST + match.statementStart = true; + var pstack = stack; + stack = []; + stack.desc = 'statement-parent'; + stack.nextBlack = match.tokposb; + pstack.push(stack); + + // list of labels, these are bound to statements (and can access any label higher up, but not cross functions) + var labelBackup = this.statementLabels; + this.statementLabels = [labelBackup]; // make ref like tree. we need this to catch labels parsed beyond the current position (not yet known to use) + stack.labels = this.statementLabels; + } //#endif + + if (match.name == 2/*IDENTIFIER*/) { + // try to determine whether it's a statement + // (block/empty statements come later, this branch is only for identifiers) + switch (match.value) { + case 'var': + match = this.eatVar(match, stack); + break; + case 'if': + match = this.eatIf(match, stack); + break; + case 'do': + match = this.eatDo(match, stack); + break; + case 'while': + match = this.eatWhile(match, stack); + break; + case 'for': + match = this.eatFor(match, stack); + break; + case 'continue': + match = this.eatContinue(match, stack); + break; + case 'break': + match = this.eatBreak(match, stack); + break; + case 'return': + match = this.eatReturn(match, stack); + break; + case 'throw': + match = this.eatThrow(match, stack); + break; + case 'switch': + match = this.eatSwitch(match, stack); + break; + case 'try': + match = this.eatTryCatchFinally(match, stack); + break; + case 'debugger': + match = this.eatDebugger(match, stack); + break; + case 'with': + match = this.eatWith(match, stack); + break; + case 'function': + // I'm not sure whether this is at all possible.... (but it's bad, either way ;) + // so add an error token, but parse the function as if it was a declaration. + this.failignore('StatementMayNotStartWithFunction', match, stack); + + // now parse as declaration... (most likely?) + match = this.eatFunctionDeclaration(match, stack); + + break; + default: // either a label or an expression-statement + match = this.eatLabelOrExpression(match, stack); + } + } else if (match.value == '{') { // Block (make sure you do this before checking for expression...) + match = this.eatBlock(match, stack); + } else if ( + // expression statements: + match.isString || + match.isNumber || + match.name == 1/*REG_EX*/ || + this.regexLhsStart.test(match.value) + ) { + match = this.eatExpressions(false, match,stack); + match = this.eatSemiColon(match, stack); + } else if (match.value == ';') { // empty statement + match.emptyStatement = true; + match = this.eatSemiColon(match, stack); + } else if (!isOptional) { + if (this.ast) { //#ifdef FULL_AST + // unmark token as being start of a statement, since it's obviously not + match.statementStart = false; + } //#endif + match = this.failsafe('UnableToParseStatement', match); + } else { + // unmark token as being start of a statement, since it's obviously not + if (this.ast) match.statementStart = true; + } + + if (this.ast) { //#ifdef FULL_AST + if (!stack.length) pstack.length = pstack.length-1; + + // restore label set + this.statementLabels = labelBackup; + } //#endif + + return match; + }, + + eatSourceElements: function(match, stack){ + //this.stats.eatSourceElements = (+//this.stats.eatSourceElements||0)+1; + // detecting the start of a statement "quickly" is virtually impossible. + // instead we keep eating statements until the match stops changing + // the first argument indicates that the statement is optional. if that + // statement was not found, the input match will also be the output. + while (match != oldMatch) { // difficult to determine whether ` && match.name != 12/*EOF*/` is actually speeding things up. it's an extra check vs one less call to eatStatement... + var oldMatch = match; + // always try to eat function declaration first. otherwise 'function' at the start might cause eatStatement to throw up + if (match.value == 'function') match = this.eatFunctionDeclaration(match, stack); + else match = this.eatStatement(true, match, stack); + } + return match; + }, + + failsafe: function(name, match, doNotAddMatch){ + var pe = new ZeParser.Error(name, match); + this.errorStack.push(pe); + + if (!doNotAddMatch) { + // the match was bad, but add it to the ast anyways. in most cases this is the case but in some its not. + // the tokenizer will pick up on the errorEscape property and add it after the match we passed on. + if (this.tokenizer.errorEscape) this.stack.push(this.tokenizer.errorEscape); + this.tokenizer.errorEscape = match; + } + var error = {start:match.start,stop:match.start,len:0, name:14/*error*/,error:pe, value:''}; + this.tokenizer.addTokenToStreamBefore(error, match); + return error; + }, + failignore: function(name, match, stack){ + var pe = new ZeParser.Error(name, match); + this.errorStack.push(pe); + // ignore the error (this will screw up :) + var error = {start:match.start,stop:match.start,len:0,name:14/*error*/,error:pe, value:''}; + stack.push(error); + this.tokenizer.addTokenToStreamBefore(error, match); + }, + failSpecial: function(error, match, stack){ + // we cant really ignore this. eat the token + stack.push(error); + this.tokenizer.addTokenToStreamBefore(error, match); + }, + +0:0}; + +//#ifdef TEST_SUITE +ZeParser.testSuite = function(tests){ + var ok = 0; + var fail = 0; + var start = +new Date; + for (var i = 0; i < tests.length; ++i) { + var test = tests[i], input = test[0], outputLen = test[1].length ? test[1][1] : test[1], desc = test[test.length - 1], stack = []; + try { + var result = ZeParser.parse(input, true); + if (result.length == outputLen) { + ++ok; + } else { + ++fail; + } + } catch (e) { + ++fail; + } + document.getElementsByTagName('div')[0].innerHTML = ('Ze parser test suite finished ('+(+new Date - start)+' ms). ok:'+ok+', fail:'+fail); + }; +}; +//#endif + +ZeParser.regexLhsStart = /[\+\-\~\!\(\{\[]/; +/* +ZeParser.regexStartKeyword = /[bcdefinrstvw]/; +ZeParser.regexKeyword = /^break$|^catch$|^continue$|^debugger$|^default$|^delete$|^do$|^else$|^finally$|^for$|^function$|^if$|^in$|^instanceof$|^new$|^return$|^switch$|^this$|^throw$|^try$|^typeof$|^var$|^void$|^while$|^with$/; +ZeParser.regexStartReserved = /[ceis]/; +ZeParser.regexReserved = /^class$|^const$|^enum$|^export$|^extends$|^import$|^super$/; +*/ +ZeParser.regexStartKeyOrReserved = /[bcdefinrstvw]/; +ZeParser.hashStartKeyOrReserved = Object.create ? Object.create(null, {b:{value:1},c:{value:1},d:{value:1},e:{value:1},f:{value:1},i:{value:1},n:{value:1},r:{value:1},s:{value:1},t:{value:1},v:{value:1},w:{value:1}}) : {b:1,c:1,d:1,e:1,f:1,i:1,n:1,r:1,s:1,t:1,v:1,w:1}; +ZeParser.regexIsKeywordOrReserved = /^break$|^catch$|^continue$|^debugger$|^default$|^delete$|^do$|^else$|^finally$|^for$|^function$|^if$|^in$|^instanceof$|^new$|^return$|^switch$|^case$|^this$|^true$|^false$|^null$|^throw$|^try$|^typeof$|^var$|^void$|^while$|^with$|^class$|^const$|^enum$|^export$|^extends$|^import$|^super$/; +ZeParser.regexAssignments = /^[\+\-\*\%\&\|\^\/]?=$|^\<\<\=$|^\>{2,3}\=$/; +ZeParser.regexNonAssignmentBinaryExpressionOperators = /^[\+\-\*\%\|\^\&\?\/]$|^[\<\>]\=?$|^[\=\!]\=\=?$|^\<\<|\>\>\>?$|^\&\&$|^\|\|$/; +ZeParser.regexUnaryKeywords = /^delete$|^void$|^typeof$|^new$/; +ZeParser.hashUnaryKeywordStart = Object.create ? Object.create(null, {d:{value:1},v:{value:1},t:{value:1},n:{value:1}}) : {d:1,v:1,t:1,n:1}; +ZeParser.regexUnaryOperators = /[\+\-\~\!]/; +ZeParser.regexLiteralKeywords = /^this$|^null$|^true$|^false$/; + +ZeParser.Error = function(type, match){ + //if (type == 'BreakOrContinueArgMustBeJustIdentifier') throw here; + this.msg = ZeParser.Errors[type].msg; + this.before = ZeParser.Errors[type].before; + this.match = match; +}; + +ZeParser.Errors = { + NoASI: {msg:'Expected semi-colon, was unable to apply ASI'}, + ExpectedAnotherExpressionComma: {msg:'expecting another (left hand sided) expression after the comma'}, + ExpectedAnotherExpressionRhs: {msg:"expected a rhs expression"}, + UnclosedGroupingOperator: {msg:"Unclosed grouping operator"}, + GroupingShouldStartWithExpression: {msg:'The grouping operator (`(`) should start with a left hand sided expression'}, + ArrayShouldStartWithExpression: {msg:'The array literal (`[`) should start with a left hand sided expression'}, + UnclosedPropertyBracket: {msg:'Property bracket was not closed after expression (expecting `]`)'}, + IllegalPropertyNameToken: {msg:'Object literal property names can only be assigned as strings, numbers or identifiers'}, + IllegalGetterSetterNameToken: {msg:'Name of a getter/setter can only be assigned as strings, numbers or identifiers'}, + GetterSetterNameFollowedByOpenParen: {msg:'The name of the getter/setter should immediately be followed by the opening parenthesis `(`'}, + GetterHasNoArguments: {msg:'The opening parenthesis `(` of the getter should be immediately followed by the closing parenthesis `)`, the getter cannot have an argument'}, + IllegalSetterArgumentNameToken: {msg:'Expecting the name of the argument of a setter, can only be assigned as strings, numbers or identifiers'}, + SettersOnlyGetOneArgument: {msg:'Setters have one and only one argument, missing the closing parenthesis `)`'}, + SetterHeaderShouldHaveClosingParen: {msg:'After the first argument of a setter should come a closing parenthesis `)`'}, + SettersMustHaveArgument: {msg:'Setters must have exactly one argument defined'}, + UnclosedObjectLiteral: {msg:'Expected to find a comma `,` for the next expression or a closing curly brace `}` to end the object literal'}, + FunctionNameMustNotBeReserved: {msg:'Function name may not be a keyword or a reserved word'}, + ExpressionMayNotStartWithKeyword: {msg:'Expressions may not start with keywords or reserved words that are not in this list: [this, null, true, false, void, typeof, delete, new]'}, + LabelsMayOnlyBeIdentifiers: {msg:'Label names may only be defined as an identifier'}, + LabelsMayNotBeReserved: {msg:'Labels may not be a keyword or a reserved word'}, + UnknownToken: {msg:'Unknown token encountered, dont know how to proceed'}, + PropertyNamesMayOnlyBeIdentifiers: {msg:'The tokens of property names accessed through the dot operator may only be identifiers'}, + SquareBracketExpectsExpression: {msg:'The square bracket property access expects an expression'}, + SquareBracketsMayNotBeEmpty: {msg:'Square brackets may never be empty, expecting an expression'}, + UnclosedSquareBrackets: {msg:'Unclosed square bracket encountered, was expecting `]` after the expression'}, + UnclosedCallParens: {msg:'Unclosed call parenthesis, expecting `)` after the optional expression'}, + InvalidCenterTernaryExpression: {msg:'Center expression of ternary operator should be a regular expression (but may not contain the comma operator directly)'}, + UnfinishedTernaryOperator: {msg:'Encountered a ternary operator start (`?`) but did not find the required colon (`:`) after the center expression'}, + TernarySecondExpressionCanNotContainComma: {msg:'The second and third expressions of the ternary operator can/may not "directly" contain a comma operator'}, + InvalidRhsExpression: {msg:'Expected a right hand side expression after the operator (which should also be a valid lhs) but did not find one'}, + FunctionDeclarationsMustHaveName: {msg:'Function declaration must have name'}, + FunctionNameMayNotBeReserved: {msg:'Function name may not be a keyword or reserved word'}, + ExpectingFunctionHeaderStart: {msg:'Expected the opening parenthesis of the function header'}, + FunctionArgumentsCanNotBeReserved: {msg:'Function arguments may not be keywords or reserved words'}, + FunctionParametersMustBeIdentifiers: {msg:'Function arguments must be identifiers'}, + ExpectedFunctionHeaderClose: {msg:'Expected the closing parenthesis `)` of the function header'}, + ExpectedFunctionBodyCurlyOpen: {msg:'Expected the opening curly brace `{` for the function body'}, + ExpectedFunctionBodyCurlyClose: {msg:'Expected the closing curly brace `}` for the function body'}, + VarNamesMayOnlyBeIdentifiers: {msg:'Missing variable name, must be a proper identifier'}, + VarNamesCanNotBeReserved: {msg:'Variable names may not be keywords or reserved words'}, + VarInitialiserExpressionExpected: {msg:'The initialiser of the variable statement should be an expression without comma'}, + ExpectedStatementHeaderOpen: {msg:'Expected opening parenthesis `(` for statement header'}, + StatementHeaderIsNotOptional: {msg:'Statement header must not be empty'}, + ExpectedStatementHeaderClose: {msg:'Expected closing parenthesis `)` for statement header'}, + DoShouldBeFollowedByWhile: {msg:'The do-while statement requires the `while` keyword after the expression'}, + ExpectedSecondSemiOfForHeader: {msg:'Expected the second semi-colon of the for-each header'}, + ForHeaderShouldHaveSemisOrIn: {msg:'The for-header should contain at least the `in` operator or two semi-colons (`;`)'}, + SwitchBodyStartsWithCurly: {msg:'The body of a switch statement starts with a curly brace `{`'}, + SwitchClausesEndWithColon: {msg:'Switch clauses (`case` and `default`) end with a colon (`:`)'}, + SwitchCannotHaveDoubleDefault: {msg:'Switches cannot have more than one `default` clause'}, + SwitchBodyEndsWithCurly: {msg:'The body of a switch statement ends with a curly brace `}`'}, + MissingTryBlockCurlyOpen: {msg:'Missing the opening curly brace (`{`) for the block of the try statement'}, + MissingTryBlockCurlyClose: {msg:'Missing the closing curly brace (`}`) for the block of the try statement'}, + CatchHeaderMissingOpen: {msg:'Missing the opening parenthesis of the catch header'}, + MissingCatchParameter: {msg:'Catch clauses should have exactly one argument which will be bound to the error object being thrown'}, + CatchParameterNameMayNotBeReserved: {msg:'Catch clause parameter may not be a keyword or reserved word'}, + CatchHeaderMissingClose: {msg:'Missing the closing parenthesis of the catch header'}, + MissingCatchBlockCurlyOpen: {msg:'Missing the opening curly brace (`{`) for the block of the catch statement'}, + MissingCatchBlockCurlyClose: {msg:'Missing the closing curly brace (`}`) for the block of the catch statement'}, + MissingFinallyBlockCurlyOpen: {msg:'Missing the opening curly brace (`{`) for the block of the finally statement'}, + MissingFinallyBlockCurlyClose: {msg:'Missing the closing curly brace (`}`) for the block of the finally statement'}, + StatementMayNotStartWithFunction: {msg:'statements may not start with function...', before:true}, + BlockCurlyClose: {msg:'Expected the closing curly (`}`) for a block statement'}, + BlockCurlyOpen: {msg:'Expected the closing curly (`}`) for a block statement'}, + UnableToParseStatement: {msg:'Was unable to find a statement when it was requested'}, + IllegalDoubleCommaInObjectLiteral: {msg:'A double comma in object literals is not allowed'}, + ObjectLiteralExpectsColonAfterName: {msg:'After every property name (identifier, string or number) a colon (`:`) should follow'}, + ThrowMustHaveArgument: {msg:'The expression argument for throw is not optional'}, + ThrowCannotHaveReturn: {msg:'There may not be a return between throw and the start of its expression argument'}, + SwitchBodyMustStartWithClause: {msg:'The body of a switch clause must start with at a case or default clause (but may be empty, which would be silly)'}, + BreakOrContinueArgMustBeJustIdentifier: {msg:'The argument to a break or continue statement must be exactly and only an identifier (an existing label)'}, + AssignmentNotAllowedAfterNonAssignmentInExpression: {msg:'An assignment is not allowed if it is preceeded by a non-expression operator in the same expression-level'}, + IllegalLhsForAssignment: {msg:'Illegal left hand side for assignment (you cannot assign to things like string literals, number literals or function calls}'}, + VarKeywordMissingName: {msg:'Var keyword should be followed by a variable name'}, + IllegalTrailingComma: {msg:'Illegal trailing comma found'}, + ObjectLiteralMissingPropertyValue: {msg:'Missing object literal property value'}, + TokenizerError: {msg:'Tokenizer encountered unexpected input'}, + LabelRequiresStatement: {msg:'Saw a label without the (required) statement following'}, + DidNotExpectElseHere: {msg:'Did not expect an else here. To what if should it belong? Maybe you put a ; after the if-block? (if(x){};else{})'}, + UnexpectedToken: {msg:'Found an unexpected token and have no idea why'}, + InvalidPostfixOperandArray: {msg:'You cannot apply ++ or -- to an array'}, + InvalidPostfixOperandObject: {msg:'You cannot apply ++ or -- to an object'}, + InvalidPostfixOperandFunction: {msg:'You cannot apply ++ or -- to a function'}, + CaseMissingExpression: {msg:'Case expects an expression before the colon'}, + TryMustHaveCatchOrFinally: {msg:'The try statement must have a catch or finally block'}, + UnexpectedInputSwitch: {msg:'Unexpected input while parsing a switch clause...'}, + ForInCanOnlyDeclareOnVar: {msg:'For-in header can only introduce one new variable'} +}; diff --git a/node_modules/esprima/test/3rdparty/backbone-0.5.3.js b/node_modules/esprima/test/3rdparty/backbone-0.5.3.js new file mode 100644 index 000000000..b2e49322b --- /dev/null +++ b/node_modules/esprima/test/3rdparty/backbone-0.5.3.js @@ -0,0 +1,1158 @@ +// Backbone.js 0.5.3 +// (c) 2010 Jeremy Ashkenas, DocumentCloud Inc. +// Backbone may be freely distributed under the MIT license. +// For all details and documentation: +// http://documentcloud.github.com/backbone + +(function(){ + + // Initial Setup + // ------------- + + // Save a reference to the global object. + var root = this; + + // Save the previous value of the `Backbone` variable. + var previousBackbone = root.Backbone; + + // The top-level namespace. All public Backbone classes and modules will + // be attached to this. Exported for both CommonJS and the browser. + var Backbone; + if (typeof exports !== 'undefined') { + Backbone = exports; + } else { + Backbone = root.Backbone = {}; + } + + // Current version of the library. Keep in sync with `package.json`. + Backbone.VERSION = '0.5.3'; + + // Require Underscore, if we're on the server, and it's not already present. + var _ = root._; + if (!_ && (typeof require !== 'undefined')) _ = require('underscore')._; + + // For Backbone's purposes, jQuery or Zepto owns the `$` variable. + var $ = root.jQuery || root.Zepto; + + // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable + // to its previous owner. Returns a reference to this Backbone object. + Backbone.noConflict = function() { + root.Backbone = previousBackbone; + return this; + }; + + // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option will + // fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and set a + // `X-Http-Method-Override` header. + Backbone.emulateHTTP = false; + + // Turn on `emulateJSON` to support legacy servers that can't deal with direct + // `application/json` requests ... will encode the body as + // `application/x-www-form-urlencoded` instead and will send the model in a + // form param named `model`. + Backbone.emulateJSON = false; + + // Backbone.Events + // ----------------- + + // A module that can be mixed in to *any object* in order to provide it with + // custom events. You may `bind` or `unbind` a callback function to an event; + // `trigger`-ing an event fires all callbacks in succession. + // + // var object = {}; + // _.extend(object, Backbone.Events); + // object.bind('expand', function(){ alert('expanded'); }); + // object.trigger('expand'); + // + Backbone.Events = { + + // Bind an event, specified by a string name, `ev`, to a `callback` function. + // Passing `"all"` will bind the callback to all events fired. + bind : function(ev, callback, context) { + var calls = this._callbacks || (this._callbacks = {}); + var list = calls[ev] || (calls[ev] = []); + list.push([callback, context]); + return this; + }, + + // Remove one or many callbacks. If `callback` is null, removes all + // callbacks for the event. If `ev` is null, removes all bound callbacks + // for all events. + unbind : function(ev, callback) { + var calls; + if (!ev) { + this._callbacks = {}; + } else if (calls = this._callbacks) { + if (!callback) { + calls[ev] = []; + } else { + var list = calls[ev]; + if (!list) return this; + for (var i = 0, l = list.length; i < l; i++) { + if (list[i] && callback === list[i][0]) { + list[i] = null; + break; + } + } + } + } + return this; + }, + + // Trigger an event, firing all bound callbacks. Callbacks are passed the + // same arguments as `trigger` is, apart from the event name. + // Listening for `"all"` passes the true event name as the first argument. + trigger : function(eventName) { + var list, calls, ev, callback, args; + var both = 2; + if (!(calls = this._callbacks)) return this; + while (both--) { + ev = both ? eventName : 'all'; + if (list = calls[ev]) { + for (var i = 0, l = list.length; i < l; i++) { + if (!(callback = list[i])) { + list.splice(i, 1); i--; l--; + } else { + args = both ? Array.prototype.slice.call(arguments, 1) : arguments; + callback[0].apply(callback[1] || this, args); + } + } + } + } + return this; + } + + }; + + // Backbone.Model + // -------------- + + // Create a new model, with defined attributes. A client id (`cid`) + // is automatically generated and assigned for you. + Backbone.Model = function(attributes, options) { + var defaults; + attributes || (attributes = {}); + if (defaults = this.defaults) { + if (_.isFunction(defaults)) defaults = defaults.call(this); + attributes = _.extend({}, defaults, attributes); + } + this.attributes = {}; + this._escapedAttributes = {}; + this.cid = _.uniqueId('c'); + this.set(attributes, {silent : true}); + this._changed = false; + this._previousAttributes = _.clone(this.attributes); + if (options && options.collection) this.collection = options.collection; + this.initialize(attributes, options); + }; + + // Attach all inheritable methods to the Model prototype. + _.extend(Backbone.Model.prototype, Backbone.Events, { + + // A snapshot of the model's previous attributes, taken immediately + // after the last `"change"` event was fired. + _previousAttributes : null, + + // Has the item been changed since the last `"change"` event? + _changed : false, + + // The default name for the JSON `id` attribute is `"id"`. MongoDB and + // CouchDB users may want to set this to `"_id"`. + idAttribute : 'id', + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize : function(){}, + + // Return a copy of the model's `attributes` object. + toJSON : function() { + return _.clone(this.attributes); + }, + + // Get the value of an attribute. + get : function(attr) { + return this.attributes[attr]; + }, + + // Get the HTML-escaped value of an attribute. + escape : function(attr) { + var html; + if (html = this._escapedAttributes[attr]) return html; + var val = this.attributes[attr]; + return this._escapedAttributes[attr] = escapeHTML(val == null ? '' : '' + val); + }, + + // Returns `true` if the attribute contains a value that is not null + // or undefined. + has : function(attr) { + return this.attributes[attr] != null; + }, + + // Set a hash of model attributes on the object, firing `"change"` unless you + // choose to silence it. + set : function(attrs, options) { + + // Extract attributes and options. + options || (options = {}); + if (!attrs) return this; + if (attrs.attributes) attrs = attrs.attributes; + var now = this.attributes, escaped = this._escapedAttributes; + + // Run validation. + if (!options.silent && this.validate && !this._performValidation(attrs, options)) return false; + + // Check for changes of `id`. + if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; + + // We're about to start triggering change events. + var alreadyChanging = this._changing; + this._changing = true; + + // Update attributes. + for (var attr in attrs) { + var val = attrs[attr]; + if (!_.isEqual(now[attr], val)) { + now[attr] = val; + delete escaped[attr]; + this._changed = true; + if (!options.silent) this.trigger('change:' + attr, this, val, options); + } + } + + // Fire the `"change"` event, if the model has been changed. + if (!alreadyChanging && !options.silent && this._changed) this.change(options); + this._changing = false; + return this; + }, + + // Remove an attribute from the model, firing `"change"` unless you choose + // to silence it. `unset` is a noop if the attribute doesn't exist. + unset : function(attr, options) { + if (!(attr in this.attributes)) return this; + options || (options = {}); + var value = this.attributes[attr]; + + // Run validation. + var validObj = {}; + validObj[attr] = void 0; + if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false; + + // Remove the attribute. + delete this.attributes[attr]; + delete this._escapedAttributes[attr]; + if (attr == this.idAttribute) delete this.id; + this._changed = true; + if (!options.silent) { + this.trigger('change:' + attr, this, void 0, options); + this.change(options); + } + return this; + }, + + // Clear all attributes on the model, firing `"change"` unless you choose + // to silence it. + clear : function(options) { + options || (options = {}); + var attr; + var old = this.attributes; + + // Run validation. + var validObj = {}; + for (attr in old) validObj[attr] = void 0; + if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false; + + this.attributes = {}; + this._escapedAttributes = {}; + this._changed = true; + if (!options.silent) { + for (attr in old) { + this.trigger('change:' + attr, this, void 0, options); + } + this.change(options); + } + return this; + }, + + // Fetch the model from the server. If the server's representation of the + // model differs from its current attributes, they will be overriden, + // triggering a `"change"` event. + fetch : function(options) { + options || (options = {}); + var model = this; + var success = options.success; + options.success = function(resp, status, xhr) { + if (!model.set(model.parse(resp, xhr), options)) return false; + if (success) success(model, resp); + }; + options.error = wrapError(options.error, model, options); + return (this.sync || Backbone.sync).call(this, 'read', this, options); + }, + + // Set a hash of model attributes, and sync the model to the server. + // If the server returns an attributes hash that differs, the model's + // state will be `set` again. + save : function(attrs, options) { + options || (options = {}); + if (attrs && !this.set(attrs, options)) return false; + var model = this; + var success = options.success; + options.success = function(resp, status, xhr) { + if (!model.set(model.parse(resp, xhr), options)) return false; + if (success) success(model, resp, xhr); + }; + options.error = wrapError(options.error, model, options); + var method = this.isNew() ? 'create' : 'update'; + return (this.sync || Backbone.sync).call(this, method, this, options); + }, + + // Destroy this model on the server if it was already persisted. Upon success, the model is removed + // from its collection, if it has one. + destroy : function(options) { + options || (options = {}); + if (this.isNew()) return this.trigger('destroy', this, this.collection, options); + var model = this; + var success = options.success; + options.success = function(resp) { + model.trigger('destroy', model, model.collection, options); + if (success) success(model, resp); + }; + options.error = wrapError(options.error, model, options); + return (this.sync || Backbone.sync).call(this, 'delete', this, options); + }, + + // Default URL for the model's representation on the server -- if you're + // using Backbone's restful methods, override this to change the endpoint + // that will be called. + url : function() { + var base = getUrl(this.collection) || this.urlRoot || urlError(); + if (this.isNew()) return base; + return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id); + }, + + // **parse** converts a response into the hash of attributes to be `set` on + // the model. The default implementation is just to pass the response along. + parse : function(resp, xhr) { + return resp; + }, + + // Create a new model with identical attributes to this one. + clone : function() { + return new this.constructor(this); + }, + + // A model is new if it has never been saved to the server, and lacks an id. + isNew : function() { + return this.id == null; + }, + + // Call this method to manually fire a `change` event for this model. + // Calling this will cause all objects observing the model to update. + change : function(options) { + this.trigger('change', this, options); + this._previousAttributes = _.clone(this.attributes); + this._changed = false; + }, + + // Determine if the model has changed since the last `"change"` event. + // If you specify an attribute name, determine if that attribute has changed. + hasChanged : function(attr) { + if (attr) return this._previousAttributes[attr] != this.attributes[attr]; + return this._changed; + }, + + // Return an object containing all the attributes that have changed, or false + // if there are no changed attributes. Useful for determining what parts of a + // view need to be updated and/or what attributes need to be persisted to + // the server. + changedAttributes : function(now) { + now || (now = this.attributes); + var old = this._previousAttributes; + var changed = false; + for (var attr in now) { + if (!_.isEqual(old[attr], now[attr])) { + changed = changed || {}; + changed[attr] = now[attr]; + } + } + return changed; + }, + + // Get the previous value of an attribute, recorded at the time the last + // `"change"` event was fired. + previous : function(attr) { + if (!attr || !this._previousAttributes) return null; + return this._previousAttributes[attr]; + }, + + // Get all of the attributes of the model at the time of the previous + // `"change"` event. + previousAttributes : function() { + return _.clone(this._previousAttributes); + }, + + // Run validation against a set of incoming attributes, returning `true` + // if all is well. If a specific `error` callback has been passed, + // call that instead of firing the general `"error"` event. + _performValidation : function(attrs, options) { + var error = this.validate(attrs); + if (error) { + if (options.error) { + options.error(this, error, options); + } else { + this.trigger('error', this, error, options); + } + return false; + } + return true; + } + + }); + + // Backbone.Collection + // ------------------- + + // Provides a standard collection class for our sets of models, ordered + // or unordered. If a `comparator` is specified, the Collection will maintain + // its models in sort order, as they're added and removed. + Backbone.Collection = function(models, options) { + options || (options = {}); + if (options.comparator) this.comparator = options.comparator; + _.bindAll(this, '_onModelEvent', '_removeReference'); + this._reset(); + if (models) this.reset(models, {silent: true}); + this.initialize.apply(this, arguments); + }; + + // Define the Collection's inheritable methods. + _.extend(Backbone.Collection.prototype, Backbone.Events, { + + // The default model for a collection is just a **Backbone.Model**. + // This should be overridden in most cases. + model : Backbone.Model, + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize : function(){}, + + // The JSON representation of a Collection is an array of the + // models' attributes. + toJSON : function() { + return this.map(function(model){ return model.toJSON(); }); + }, + + // Add a model, or list of models to the set. Pass **silent** to avoid + // firing the `added` event for every new model. + add : function(models, options) { + if (_.isArray(models)) { + for (var i = 0, l = models.length; i < l; i++) { + this._add(models[i], options); + } + } else { + this._add(models, options); + } + return this; + }, + + // Remove a model, or a list of models from the set. Pass silent to avoid + // firing the `removed` event for every model removed. + remove : function(models, options) { + if (_.isArray(models)) { + for (var i = 0, l = models.length; i < l; i++) { + this._remove(models[i], options); + } + } else { + this._remove(models, options); + } + return this; + }, + + // Get a model from the set by id. + get : function(id) { + if (id == null) return null; + return this._byId[id.id != null ? id.id : id]; + }, + + // Get a model from the set by client id. + getByCid : function(cid) { + return cid && this._byCid[cid.cid || cid]; + }, + + // Get the model at the given index. + at: function(index) { + return this.models[index]; + }, + + // Force the collection to re-sort itself. You don't need to call this under normal + // circumstances, as the set will maintain sort order as each item is added. + sort : function(options) { + options || (options = {}); + if (!this.comparator) throw new Error('Cannot sort a set without a comparator'); + this.models = this.sortBy(this.comparator); + if (!options.silent) this.trigger('reset', this, options); + return this; + }, + + // Pluck an attribute from each model in the collection. + pluck : function(attr) { + return _.map(this.models, function(model){ return model.get(attr); }); + }, + + // When you have more items than you want to add or remove individually, + // you can reset the entire set with a new list of models, without firing + // any `added` or `removed` events. Fires `reset` when finished. + reset : function(models, options) { + models || (models = []); + options || (options = {}); + this.each(this._removeReference); + this._reset(); + this.add(models, {silent: true}); + if (!options.silent) this.trigger('reset', this, options); + return this; + }, + + // Fetch the default set of models for this collection, resetting the + // collection when they arrive. If `add: true` is passed, appends the + // models to the collection instead of resetting. + fetch : function(options) { + options || (options = {}); + var collection = this; + var success = options.success; + options.success = function(resp, status, xhr) { + collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options); + if (success) success(collection, resp); + }; + options.error = wrapError(options.error, collection, options); + return (this.sync || Backbone.sync).call(this, 'read', this, options); + }, + + // Create a new instance of a model in this collection. After the model + // has been created on the server, it will be added to the collection. + // Returns the model, or 'false' if validation on a new model fails. + create : function(model, options) { + var coll = this; + options || (options = {}); + model = this._prepareModel(model, options); + if (!model) return false; + var success = options.success; + options.success = function(nextModel, resp, xhr) { + coll.add(nextModel, options); + if (success) success(nextModel, resp, xhr); + }; + model.save(null, options); + return model; + }, + + // **parse** converts a response into a list of models to be added to the + // collection. The default implementation is just to pass it through. + parse : function(resp, xhr) { + return resp; + }, + + // Proxy to _'s chain. Can't be proxied the same way the rest of the + // underscore methods are proxied because it relies on the underscore + // constructor. + chain: function () { + return _(this.models).chain(); + }, + + // Reset all internal state. Called when the collection is reset. + _reset : function(options) { + this.length = 0; + this.models = []; + this._byId = {}; + this._byCid = {}; + }, + + // Prepare a model to be added to this collection + _prepareModel: function(model, options) { + if (!(model instanceof Backbone.Model)) { + var attrs = model; + model = new this.model(attrs, {collection: this}); + if (model.validate && !model._performValidation(attrs, options)) model = false; + } else if (!model.collection) { + model.collection = this; + } + return model; + }, + + // Internal implementation of adding a single model to the set, updating + // hash indexes for `id` and `cid` lookups. + // Returns the model, or 'false' if validation on a new model fails. + _add : function(model, options) { + options || (options = {}); + model = this._prepareModel(model, options); + if (!model) return false; + var already = this.getByCid(model); + if (already) throw new Error(["Can't add the same model to a set twice", already.id]); + this._byId[model.id] = model; + this._byCid[model.cid] = model; + var index = options.at != null ? options.at : + this.comparator ? this.sortedIndex(model, this.comparator) : + this.length; + this.models.splice(index, 0, model); + model.bind('all', this._onModelEvent); + this.length++; + if (!options.silent) model.trigger('add', model, this, options); + return model; + }, + + // Internal implementation of removing a single model from the set, updating + // hash indexes for `id` and `cid` lookups. + _remove : function(model, options) { + options || (options = {}); + model = this.getByCid(model) || this.get(model); + if (!model) return null; + delete this._byId[model.id]; + delete this._byCid[model.cid]; + this.models.splice(this.indexOf(model), 1); + this.length--; + if (!options.silent) model.trigger('remove', model, this, options); + this._removeReference(model); + return model; + }, + + // Internal method to remove a model's ties to a collection. + _removeReference : function(model) { + if (this == model.collection) { + delete model.collection; + } + model.unbind('all', this._onModelEvent); + }, + + // Internal method called every time a model in the set fires an event. + // Sets need to update their indexes when models change ids. All other + // events simply proxy through. "add" and "remove" events that originate + // in other collections are ignored. + _onModelEvent : function(ev, model, collection, options) { + if ((ev == 'add' || ev == 'remove') && collection != this) return; + if (ev == 'destroy') { + this._remove(model, options); + } + if (model && ev === 'change:' + model.idAttribute) { + delete this._byId[model.previous(model.idAttribute)]; + this._byId[model.id] = model; + } + this.trigger.apply(this, arguments); + } + + }); + + // Underscore methods that we want to implement on the Collection. + var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', 'detect', + 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include', + 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size', + 'first', 'rest', 'last', 'without', 'indexOf', 'lastIndexOf', 'isEmpty', 'groupBy']; + + // Mix in each Underscore method as a proxy to `Collection#models`. + _.each(methods, function(method) { + Backbone.Collection.prototype[method] = function() { + return _[method].apply(_, [this.models].concat(_.toArray(arguments))); + }; + }); + + // Backbone.Router + // ------------------- + + // Routers map faux-URLs to actions, and fire events when routes are + // matched. Creating a new one sets its `routes` hash, if not set statically. + Backbone.Router = function(options) { + options || (options = {}); + if (options.routes) this.routes = options.routes; + this._bindRoutes(); + this.initialize.apply(this, arguments); + }; + + // Cached regular expressions for matching named param parts and splatted + // parts of route strings. + var namedParam = /:([\w\d]+)/g; + var splatParam = /\*([\w\d]+)/g; + var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g; + + // Set up all inheritable **Backbone.Router** properties and methods. + _.extend(Backbone.Router.prototype, Backbone.Events, { + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize : function(){}, + + // Manually bind a single named route to a callback. For example: + // + // this.route('search/:query/p:num', 'search', function(query, num) { + // ... + // }); + // + route : function(route, name, callback) { + Backbone.history || (Backbone.history = new Backbone.History); + if (!_.isRegExp(route)) route = this._routeToRegExp(route); + Backbone.history.route(route, _.bind(function(fragment) { + var args = this._extractParameters(route, fragment); + callback.apply(this, args); + this.trigger.apply(this, ['route:' + name].concat(args)); + }, this)); + }, + + // Simple proxy to `Backbone.history` to save a fragment into the history. + navigate : function(fragment, triggerRoute) { + Backbone.history.navigate(fragment, triggerRoute); + }, + + // Bind all defined routes to `Backbone.history`. We have to reverse the + // order of the routes here to support behavior where the most general + // routes can be defined at the bottom of the route map. + _bindRoutes : function() { + if (!this.routes) return; + var routes = []; + for (var route in this.routes) { + routes.unshift([route, this.routes[route]]); + } + for (var i = 0, l = routes.length; i < l; i++) { + this.route(routes[i][0], routes[i][1], this[routes[i][1]]); + } + }, + + // Convert a route string into a regular expression, suitable for matching + // against the current location hash. + _routeToRegExp : function(route) { + route = route.replace(escapeRegExp, "\\$&") + .replace(namedParam, "([^\/]*)") + .replace(splatParam, "(.*?)"); + return new RegExp('^' + route + '$'); + }, + + // Given a route, and a URL fragment that it matches, return the array of + // extracted parameters. + _extractParameters : function(route, fragment) { + return route.exec(fragment).slice(1); + } + + }); + + // Backbone.History + // ---------------- + + // Handles cross-browser history management, based on URL fragments. If the + // browser does not support `onhashchange`, falls back to polling. + Backbone.History = function() { + this.handlers = []; + _.bindAll(this, 'checkUrl'); + }; + + // Cached regex for cleaning hashes. + var hashStrip = /^#*/; + + // Cached regex for detecting MSIE. + var isExplorer = /msie [\w.]+/; + + // Has the history handling already been started? + var historyStarted = false; + + // Set up all inheritable **Backbone.History** properties and methods. + _.extend(Backbone.History.prototype, { + + // The default interval to poll for hash changes, if necessary, is + // twenty times a second. + interval: 50, + + // Get the cross-browser normalized URL fragment, either from the URL, + // the hash, or the override. + getFragment : function(fragment, forcePushState) { + if (fragment == null) { + if (this._hasPushState || forcePushState) { + fragment = window.location.pathname; + var search = window.location.search; + if (search) fragment += search; + if (fragment.indexOf(this.options.root) == 0) fragment = fragment.substr(this.options.root.length); + } else { + fragment = window.location.hash; + } + } + return decodeURIComponent(fragment.replace(hashStrip, '')); + }, + + // Start the hash change handling, returning `true` if the current URL matches + // an existing route, and `false` otherwise. + start : function(options) { + + // Figure out the initial configuration. Do we need an iframe? + // Is pushState desired ... is it available? + if (historyStarted) throw new Error("Backbone.history has already been started"); + this.options = _.extend({}, {root: '/'}, this.options, options); + this._wantsPushState = !!this.options.pushState; + this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState); + var fragment = this.getFragment(); + var docMode = document.documentMode; + var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7)); + if (oldIE) { + this.iframe = $('