diff --git a/.gitmodules b/.gitmodules index aee8d549..6074430c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "support/node-o3-xml"] - path = support/node-o3-xml - url = git://github.com/ajaxorg/node-o3-xml.git [submodule "support/cockpit"] path = support/cockpit url = git://github.com/ajaxorg/cockpit.git diff --git a/demo/icons/Readme.txt b/demo/icons/Readme.txt new file mode 100644 index 00000000..87a71c8d --- /dev/null +++ b/demo/icons/Readme.txt @@ -0,0 +1 @@ +The icons in this folder are from the Eclipse project and licensed under the Eclipse public license version 1.0 (EPL). \ No newline at end of file diff --git a/demo/icons/epl.html b/demo/icons/epl.html new file mode 100644 index 00000000..f57b834b --- /dev/null +++ b/demo/icons/epl.html @@ -0,0 +1,260 @@ + + + + +Eclipse Public License - Version 1.0 + + + + + + +

Eclipse Public License - v 1.0

+ +

THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE +PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR +DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS +AGREEMENT.

+ +

1. DEFINITIONS

+ +

"Contribution" means:

+ +

a) in the case of the initial Contributor, the initial +code and documentation distributed under this Agreement, and

+

b) in the case of each subsequent Contributor:

+

i) changes to the Program, and

+

ii) additions to the Program;

+

where such changes and/or additions to the Program +originate from and are distributed by that particular Contributor. A +Contribution 'originates' from a Contributor if it was added to the +Program by such Contributor itself or anyone acting on such +Contributor's behalf. Contributions do not include additions to the +Program which: (i) are separate modules of software distributed in +conjunction with the Program under their own license agreement, and (ii) +are not derivative works of the Program.

+ +

"Contributor" means any person or entity that distributes +the Program.

+ +

"Licensed Patents" mean patent claims licensable by a +Contributor which are necessarily infringed by the use or sale of its +Contribution alone or when combined with the Program.

+ +

"Program" means the Contributions distributed in accordance +with this Agreement.

+ +

"Recipient" means anyone who receives the Program under +this Agreement, including all Contributors.

+ +

2. GRANT OF RIGHTS

+ +

a) Subject to the terms of this Agreement, each +Contributor hereby grants Recipient a non-exclusive, worldwide, +royalty-free copyright license to reproduce, prepare derivative works +of, publicly display, publicly perform, distribute and sublicense the +Contribution of such Contributor, if any, and such derivative works, in +source code and object code form.

+ +

b) Subject to the terms of this Agreement, each +Contributor hereby grants Recipient a non-exclusive, worldwide, +royalty-free patent license under Licensed Patents to make, use, sell, +offer to sell, import and otherwise transfer the Contribution of such +Contributor, if any, in source code and object code form. This patent +license shall apply to the combination of the Contribution and the +Program if, at the time the Contribution is added by the Contributor, +such addition of the Contribution causes such combination to be covered +by the Licensed Patents. The patent license shall not apply to any other +combinations which include the Contribution. No hardware per se is +licensed hereunder.

+ +

c) Recipient understands that although each Contributor +grants the licenses to its Contributions set forth herein, no assurances +are provided by any Contributor that the Program does not infringe the +patent or other intellectual property rights of any other entity. Each +Contributor disclaims any liability to Recipient for claims brought by +any other entity based on infringement of intellectual property rights +or otherwise. As a condition to exercising the rights and licenses +granted hereunder, each Recipient hereby assumes sole responsibility to +secure any other intellectual property rights needed, if any. For +example, if a third party patent license is required to allow Recipient +to distribute the Program, it is Recipient's responsibility to acquire +that license before distributing the Program.

+ +

d) Each Contributor represents that to its knowledge it +has sufficient copyright rights in its Contribution, if any, to grant +the copyright license set forth in this Agreement.

+ +

3. REQUIREMENTS

+ +

A Contributor may choose to distribute the Program in object code +form under its own license agreement, provided that:

+ +

a) it complies with the terms and conditions of this +Agreement; and

+ +

b) its license agreement:

+ +

i) effectively disclaims on behalf of all Contributors +all warranties and conditions, express and implied, including warranties +or conditions of title and non-infringement, and implied warranties or +conditions of merchantability and fitness for a particular purpose;

+ +

ii) effectively excludes on behalf of all Contributors +all liability for damages, including direct, indirect, special, +incidental and consequential damages, such as lost profits;

+ +

iii) states that any provisions which differ from this +Agreement are offered by that Contributor alone and not by any other +party; and

+ +

iv) states that source code for the Program is available +from such Contributor, and informs licensees how to obtain it in a +reasonable manner on or through a medium customarily used for software +exchange.

+ +

When the Program is made available in source code form:

+ +

a) it must be made available under this Agreement; and

+ +

b) a copy of this Agreement must be included with each +copy of the Program.

+ +

Contributors may not remove or alter any copyright notices contained +within the Program.

+ +

Each Contributor must identify itself as the originator of its +Contribution, if any, in a manner that reasonably allows subsequent +Recipients to identify the originator of the Contribution.

+ +

4. COMMERCIAL DISTRIBUTION

+ +

Commercial distributors of software may accept certain +responsibilities with respect to end users, business partners and the +like. While this license is intended to facilitate the commercial use of +the Program, the Contributor who includes the Program in a commercial +product offering should do so in a manner which does not create +potential liability for other Contributors. Therefore, if a Contributor +includes the Program in a commercial product offering, such Contributor +("Commercial Contributor") hereby agrees to defend and +indemnify every other Contributor ("Indemnified Contributor") +against any losses, damages and costs (collectively "Losses") +arising from claims, lawsuits and other legal actions brought by a third +party against the Indemnified Contributor to the extent caused by the +acts or omissions of such Commercial Contributor in connection with its +distribution of the Program in a commercial product offering. The +obligations in this section do not apply to any claims or Losses +relating to any actual or alleged intellectual property infringement. In +order to qualify, an Indemnified Contributor must: a) promptly notify +the Commercial Contributor in writing of such claim, and b) allow the +Commercial Contributor to control, and cooperate with the Commercial +Contributor in, the defense and any related settlement negotiations. The +Indemnified Contributor may participate in any such claim at its own +expense.

+ +

For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have to +defend claims against the other Contributors related to those +performance claims and warranties, and if a court requires any other +Contributor to pay any damages as a result, the Commercial Contributor +must pay those damages.

+ +

5. NO WARRANTY

+ +

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS +PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS +OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, +ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY +OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely +responsible for determining the appropriateness of using and +distributing the Program and assumes all risks associated with its +exercise of rights under this Agreement , including but not limited to +the risks and costs of program errors, compliance with applicable laws, +damage to or loss of data, programs or equipment, and unavailability or +interruption of operations.

+ +

6. DISCLAIMER OF LIABILITY

+ +

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT +NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING +WITHOUT LIMITATION LOST PROFITS), 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 OR +DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED +HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

+ +

7. GENERAL

+ +

If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further action +by the parties hereto, such provision shall be reformed to the minimum +extent necessary to make such provision valid and enforceable.

+ +

If Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other +software or hardware) infringes such Recipient's patent(s), then such +Recipient's rights granted under Section 2(b) shall terminate as of the +date such litigation is filed.

+ +

All Recipient's rights under this Agreement shall terminate if it +fails to comply with any of the material terms or conditions of this +Agreement and does not cure such failure in a reasonable period of time +after becoming aware of such noncompliance. If all Recipient's rights +under this Agreement terminate, Recipient agrees to cease use and +distribution of the Program as soon as reasonably practicable. However, +Recipient's obligations under this Agreement and any licenses granted by +Recipient relating to the Program shall continue and survive.

+ +

Everyone is permitted to copy and distribute copies of this +Agreement, but in order to avoid inconsistency the Agreement is +copyrighted and may only be modified in the following manner. The +Agreement Steward reserves the right to publish new versions (including +revisions) of this Agreement from time to time. No one other than the +Agreement Steward has the right to modify this Agreement. The Eclipse +Foundation is the initial Agreement Steward. The Eclipse Foundation may +assign the responsibility to serve as the Agreement Steward to a +suitable separate entity. Each new version of the Agreement will be +given a distinguishing version number. The Program (including +Contributions) may always be distributed subject to the version of the +Agreement under which it was received. In addition, after a new version +of the Agreement is published, Contributor may elect to distribute the +Program (including its Contributions) under the new version. Except as +expressly stated in Sections 2(a) and 2(b) above, Recipient receives no +rights or licenses to the intellectual property of any Contributor under +this Agreement, whether expressly, by implication, estoppel or +otherwise. All rights in the Program not expressly granted under this +Agreement are reserved.

+ +

This Agreement is governed by the laws of the State of New York and +the intellectual property laws of the United States of America. No party +to this Agreement will bring a legal action under this Agreement more +than one year after the cause of action arose. Each party waives its +rights to a jury trial in any resulting litigation.

+ + + + + \ No newline at end of file diff --git a/demo/icons/error_obj.gif b/demo/icons/error_obj.gif new file mode 100644 index 00000000..0bc60689 Binary files /dev/null and b/demo/icons/error_obj.gif differ diff --git a/demo/icons/warning_obj.gif b/demo/icons/warning_obj.gif new file mode 100644 index 00000000..2b2e50fe Binary files /dev/null and b/demo/icons/warning_obj.gif differ diff --git a/demo/startup.js b/demo/startup.js index b62e79e3..1403fd1b 100644 --- a/demo/startup.js +++ b/demo/startup.js @@ -58,13 +58,43 @@ exports.launch = function(env) { var vim = require("ace/keyboard/keybinding/vim").Vim; var emacs = require("ace/keyboard/keybinding/emacs").Emacs; var HashHandler = require("ace/keyboard/hash_handler").HashHandler; - + var docs = {}; docs.js = new EditSession(document.getElementById("jstext").innerHTML); docs.js.setMode(new JavaScriptMode()); docs.js.setUndoManager(new UndoManager()); - + + if (false && window.Worker) { + var worker = new WorkerClient("../..", ["ace", "pilot"], "ace/worker/mirror", "Mirror"); + worker.call("setValue", [docs.js.getValue()]); + + docs.js.getDocument().on("change", function(e) { + e.range = { + start: e.data.range.start, + end: e.data.range.end + }; + worker.emit("change", e); + }); + + worker.on("jslint", function(results) { + var errors = []; + for (var i=0; i - - - - - Editor - - - - - -
- - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - - - - - - - - - - - - - - - diff --git a/editor.html b/editor.html index f1b20bf5..30e67abb 100644 --- a/editor.html +++ b/editor.html @@ -6,66 +6,7 @@ Editor - - +
diff --git a/experiments/capture.html b/experiments/capture.html new file mode 100644 index 00000000..9df5e3cd --- /dev/null +++ b/experiments/capture.html @@ -0,0 +1,45 @@ + + + + + + + + + + + + +
+ +
+ + + + + diff --git a/experiments/cut_copy.html b/experiments/cut_copy.html new file mode 100644 index 00000000..3299f3ad --- /dev/null +++ b/experiments/cut_copy.html @@ -0,0 +1,105 @@ + + + + + + Text Events + + + + + + + +
+ +
+
+ + +
+ +
+ + + + + diff --git a/experiments/triple_click.html b/experiments/triple_click.html new file mode 100644 index 00000000..da953a94 --- /dev/null +++ b/experiments/triple_click.html @@ -0,0 +1,34 @@ + + + + + + triple_click + + + + + +
+ Juhu Kinners +
+ + + + + + + diff --git a/experiments/worker.html b/experiments/worker.html new file mode 100644 index 00000000..f3c75bde --- /dev/null +++ b/experiments/worker.html @@ -0,0 +1,33 @@ + + + + + + worker + + + + + + + + + + diff --git a/experiments/worker.js b/experiments/worker.js new file mode 100644 index 00000000..1335f55e --- /dev/null +++ b/experiments/worker.js @@ -0,0 +1,3 @@ +onmessage = function(e) { + onmessage = new Function("e", e.data); +}; \ No newline at end of file diff --git a/lib/ace/css/editor.css b/lib/ace/css/editor.css index adfa1511..f59da076 100644 --- a/lib/ace/css/editor.css +++ b/lib/ace/css/editor.css @@ -12,6 +12,20 @@ overflow-y: hidden; } +.ace_content { + position: absolute; + box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; +} + +.ace_composition { + position: absolute; + background: #555; + color: #DDD; + z-index: 4; +} + .ace_gutter { position: absolute; overflow-x: hidden; @@ -61,8 +75,14 @@ color: black; } +.ace_cjk { + display: inline-block; + text-align: center; +} + .ace_cursor-layer { cursor: text; + pointer-events: none; } .ace_cursor { diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index e9e2a66a..5ae0998c 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -52,16 +52,14 @@ var EditSession = function(text, mode) { this.selection = new Selection(this); this.$breakpoints = []; - this.listeners = []; - if (mode) { - this.setMode(mode); - } - if (text instanceof Document) { this.setDocument(text) } else { this.setDocument(new Document(text)); } + + if (mode) + this.setMode(mode); }; @@ -198,6 +196,41 @@ var EditSession = function(text, mode) { this._dispatchEvent("changeBreakpoint", {}); }; + this.getBreakpoints = function() { + return this.$breakpoints; + }; + + /** + * Error: + * { + * row: 12, + * column: 2, //can be undefined + * text: "Missing argument", + * type: "error" // or "warning" or "info" + * } + */ + this.setAnnotations = function(annotations) { + this.$annotations = []; + for (var i=0; i len) { - remaining -= (len + 1); - screenColumn += len + tabSize; - } - else { - screenColumn += remaining; - break; - } - } + var line = this.getLine(row); + + for (var i=0; i 0) { + remaining -= 1; + // tab + if (c == 9) { + screenColumn += tabSize; + } + // CJK characters + else if ( + c >= 0x3040 && c <= 0x309F || // Hiragana + c >= 0x30A0 && c <= 0x30FF || // Katakana + c >= 0x4E00 && c <= 0x9FFF || // Single CJK ideographs + c >= 0xF900 && c <= 0xFAFF || + c >= 0x3400 && c <= 0x4DBF + ) { + screenColumn += 2; + } else { + screenColumn += 1; + } + } else { + break; + } + } return screenColumn; }; @@ -616,23 +667,44 @@ var EditSession = function(text, mode) { var docColumn = 0; var remaining = screenColumn; - - var line = this.getLine(row).split("\t"); - for (var i=0; i= len + tabSize) { - remaining -= (len + tabSize); - docColumn += (len + 1); - } - else if (remaining > len){ - docColumn += len; - break; - } - else { - docColumn += remaining; - break; - } - } + var line = this.getLine(row); + + for(var i=0; i 0) { + docColumn += 1; + // tab + if (c == 9) { + if (remaining >= tabSize) { + remaining -= tabSize; + } else { + remaining = 0; + docColumn -= 1; + } + } + // CJK characters + else if ( + c >= 0x3040 && c <= 0x309F || // Hiragana + c >= 0x30A0 && c <= 0x30FF || // Katakana + c >= 0x4E00 && c <= 0x9FFF || // Single CJK ideographs + c >= 0xF900 && c <= 0xFAFF || + c >= 0x3400 && c <= 0x4DBF + ) { + if (remaining >= 2) { + remaining -= 2; + } else { + remaining = 0; + docColumn -= 1; + } + } else { + remaining -= 1; + } + } else { + break; + } + } + return docColumn; }; diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 807d1d2a..efa1cb9a 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -57,7 +57,7 @@ var Editor =function(renderer, session) { this.keyBinding = new KeyBinding(this); var self = this; event.addListener(container, "mousedown", function(e) { - setTimeout(function() {self.focus();}); + self.focus(); return event.preventDefault(e); }); event.addListener(container, "selectstart", function(e) { @@ -122,10 +122,12 @@ var Editor =function(renderer, session) { if (this.session == session) return; if (this.session) { + var oldSession = this.session; this.session.removeEventListener("change", this.$onDocumentChange); this.session.removeEventListener("changeMode", this.$onDocumentModeChange); this.session.removeEventListener("changeTabSize", this.$onDocumentChangeTabSize); this.session.removeEventListener("changeBreakpoint", this.$onDocumentChangeBreakpoint); + this.session.removeEventListener("changeAnnotation", this.$onDocumentChangeAnnotation); var selection = this.session.getSelection(); selection.removeEventListener("changeCursor", this.$onCursorChange); @@ -148,6 +150,9 @@ var Editor =function(renderer, session) { this.$onDocumentChangeBreakpoint = this.onDocumentChangeBreakpoint.bind(this); this.session.addEventListener("changeBreakpoint", this.$onDocumentChangeBreakpoint); + + this.$onDocumentChangeAnnotation = this.onDocumentChangeAnnotation.bind(this); + this.session.addEventListener("changeAnnotation", this.$onDocumentChangeAnnotation); this.selection = session.getSelection(); this.$desiredColumn = 0; @@ -165,8 +170,14 @@ var Editor =function(renderer, session) { this.onCursorChange(); this.onSelectionChange(); this.onDocumentChangeBreakpoint(); + this.onDocumentChangeAnnotation(); this.renderer.scrollToRow(session.getScrollTopRow()); this.renderer.updateFull(); + + this._dispatchEvent("changeSession", { + session: session, + oldSession: oldSession + }); }; this.getSession = function() { @@ -289,6 +300,10 @@ var Editor =function(renderer, session) { this.renderer.setBreakpoints(this.session.getBreakpoints()); }; + this.onDocumentChangeAnnotation = function() { + this.renderer.setAnnotations(this.session.getAnnotations()); + }; + this.onDocumentModeChange = function() { var mode = this.session.getMode(); if (this.mode == mode) diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index 08812a0a..459f9e0e 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -43,11 +43,34 @@ var TextInput = function(parentNode, host) { var text = document.createElement("textarea"); var style = text.style; - style.position = "absolute"; + style.position = "fixed"; style.left = "-10000px"; style.top = "-10000px"; parentNode.appendChild(text); + // move text input over the cursor + // this is required for iOS and IME + style.left = "0px"; + style.top = "0px"; + style.zIndex = -1; + style.opacity = 0; + style.width = "10px"; + style.height = "30px"; + + var changeCursor = function() { + var cursor = host.getCursorPosition(); + var pos = host.renderer.textToScreenCoordinates(cursor.row, cursor.column); + var epos = parentNode.getBoundingClientRect(); + style.left = (pos.pageX - epos.left - 6) + "px"; + style.top = (pos.pageY - epos.top + 52) + "px"; + }; + + host.addEventListener("changeSession", function(e) { + if (e.oldSession) + e.oldSession.getSelection().removeEventListener("changeCursor", changeCursor); + e.session.getSelection().addEventListener("changeCursor", changeCursor); + }); + var PLACEHOLDER = String.fromCharCode(0); sendText(); diff --git a/lib/ace/layer/gutter.js b/lib/ace/layer/gutter.js index aa3e5e7c..13c85bad 100644 --- a/lib/ace/layer/gutter.js +++ b/lib/ace/layer/gutter.js @@ -45,6 +45,7 @@ var Gutter = function(parentEl) { parentEl.appendChild(this.element); this.$breakpoints = []; + this.$annotations = []; this.$decorations = []; }; @@ -57,22 +58,49 @@ var Gutter = function(parentEl) { } this.removeGutterDecoration = function(row, className){ - this.$decorations[row] = - this.$decorations[row].replace(" ace_" + className, ""); - } + this.$decorations[row] = this.$decorations[row].replace(" ace_" + className, ""); + }; this.setBreakpoints = function(rows) { this.$breakpoints = rows.concat(); }; + this.setAnnotations = function(annotations) { + // iterate over sparse array + this.$annotations = []; + for (var row in annotations) { + var rowInfo = this.$annotations[row] = { + text: [] + }; + var rowAnnotations = annotations[row]; + for (var i=0; i", (i+1), ""); html.push(""); } diff --git a/lib/ace/layer/text.js b/lib/ace/layer/text.js index 8ef2ebf3..568f1db9 100644 --- a/lib/ace/layer/text.js +++ b/lib/ace/layer/text.js @@ -248,19 +248,26 @@ var Text = function(parentEl) { }; this.$renderLine = function(stringBuilder, row, tokens) { -// if (this.showInvisibles) { -// var self = this; -// var spaceRe = /[\v\f \u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000]+/g; -// var spaceReplace = function(space) { -// var space = new Array(space.length+1).join(self.SPACE_CHAR); -// return "" + space + ""; -// }; -// } -// else { + if (this.showInvisibles) { + var self = this; + var spaceRe = /( +)|([\v\f \u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000])/g; + var spaceReplace = function(space) { + if (space.charCodeAt(0) == 32) + return new Array(space.length+1).join(" "); + else { + var space = new Array(space.length+1).join(self.SPACE_CHAR); + return "" + space + ""; + } + + }; + } + else { var spaceRe = /[\v\f \u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000]/g; var spaceReplace = " "; -// } + } + var characterWidth = this.config.characterWidth; + for ( var i = 0; i < tokens.length; i++) { var token = tokens[i]; @@ -268,8 +275,11 @@ var Text = function(parentEl) { .replace(/&/g, "&") .replace(/" + c + "" + }); + if (!this.$textToken[token.type]) { var classes = "ace_" + token.type.replace(/\./g, " ace_"); stringBuilder.push("", output, ""); diff --git a/lib/ace/mode/css_highlight_rules.js b/lib/ace/mode/css_highlight_rules.js index 1dc74e15..f13c821e 100644 --- a/lib/ace/mode/css_highlight_rules.js +++ b/lib/ace/mode/css_highlight_rules.js @@ -44,14 +44,14 @@ var TextHighlightRules = require("ace/mode/text_highlight_rules").TextHighlightR var CssHighlightRules = function() { var properties = lang.arrayToMap( - ("azimuth|background-attachment|background-color|background-image|" + + ("-moz-box-sizing|-webkit-box-sizing|azimuth|background-attachment|background-color|background-image|" + "background-position|background-repeat|background|border-bottom-color|" + "border-bottom-style|border-bottom-width|border-bottom|border-collapse|" + "border-color|border-left-color|border-left-style|border-left-width|" + "border-left|border-right-color|border-right-style|border-right-width|" + "border-right|border-spacing|border-style|border-top-color|" + "border-top-style|border-top-width|border-top|border-width|border|" + - "bottom|caption-side|clear|clip|color|content|counter-increment|" + + "bottom|box-sizing|caption-side|clear|clip|color|content|counter-increment|" + "counter-reset|cue-after|cue-before|cue|cursor|direction|display|" + "elevation|empty-cells|float|font-family|font-size-adjust|font-size|" + "font-stretch|font-style|font-variant|font-weight|font|height|left|" + @@ -76,8 +76,8 @@ var CssHighlightRules = function() { var constants = lang.arrayToMap( ("absolute|all-scroll|always|armenian|auto|baseline|below|bidi-override|" + - "block|bold|bolder|both|bottom|break-all|break-word|capitalize|center|" + - "char|circle|cjk-ideographic|col-resize|collapse|crosshair|dashed|" + + "block|bold|bolder|border-box|both|bottom|break-all|break-word|capitalize|center|" + + "char|circle|cjk-ideographic|col-resize|collapse|content-box|crosshair|dashed|" + "decimal-leading-zero|decimal|default|disabled|disc|" + "distribute-all-lines|distribute-letter|distribute-space|" + "distribute|dotted|double|e-resize|ellipsis|fixed|georgian|groove|" + diff --git a/lib/ace/mode/javascript.js b/lib/ace/mode/javascript.js index 02f7e7ac..5aab3d37 100644 --- a/lib/ace/mode/javascript.js +++ b/lib/ace/mode/javascript.js @@ -43,6 +43,7 @@ var Tokenizer = require("ace/tokenizer").Tokenizer; var JavaScriptHighlightRules = require("ace/mode/javascript_highlight_rules").JavaScriptHighlightRules; var MatchingBraceOutdent = require("ace/mode/matching_brace_outdent").MatchingBraceOutdent; var Range = require("ace/range").Range; +var WorkerClient = require("ace/worker/worker_client").WorkerClient; var Mode = function() { this.$tokenizer = new Tokenizer(new JavaScriptHighlightRules().getRules()); @@ -120,6 +121,47 @@ oop.inherits(Mode, TextMode); this.autoOutdent = function(state, doc, row) { return this.$outdent.autoOutdent(doc, row); }; + + this.createWorker = function(session) { + var doc = session.getDocument(); + var worker = new WorkerClient("../..", ["ace", "pilot"], "ace/mode/javascript_worker", "JavaScriptWorker"); + worker.call("setValue", [doc.getValue()]); + + doc.on("change", function(e) { + e.range = { + start: e.data.range.start, + end: e.data.range.end + }; + worker.emit("change", e); + }); + + worker.on("jslint", function(results) { + var errors = []; + for (var i=0; i. + * Portions created by the Initial Developer are Copyright (C) 2004 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Tom Austin + * Brendan Eich + * Shu-Yu Guo + * Dave Herman + * Dimitris Vardoulakis + * Patrick Walton + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * Narcissus - JS implemented in JS. + * + * Well-known constants and lookup tables. Many consts are generated from the + * tokens table via eval to minimize redundancy, so consumers must be compiled + * separately to take advantage of the simple switch-case constant propagation + * done by SpiderMonkey. + */ + +define(function(require, exports, module) { + +exports.options = { + version: 185, +}; + +(function() { + exports.hostGlobal = this +})(); + +var tokens = [ + // End of source. + "END", + + // Operators and punctuators. Some pair-wise order matters, e.g. (+, -) + // and (UNARY_PLUS, UNARY_MINUS). + "\n", ";", + ",", + "=", + "?", ":", "CONDITIONAL", + "||", + "&&", + "|", + "^", + "&", + "==", "!=", "===", "!==", + "<", "<=", ">=", ">", + "<<", ">>", ">>>", + "+", "-", + "*", "/", "%", + "!", "~", "UNARY_PLUS", "UNARY_MINUS", + "++", "--", + ".", + "[", "]", + "{", "}", + "(", ")", + + // Nonterminal tree node type codes. + "SCRIPT", "BLOCK", "LABEL", "FOR_IN", "CALL", "NEW_WITH_ARGS", "INDEX", + "ARRAY_INIT", "OBJECT_INIT", "PROPERTY_INIT", "GETTER", "SETTER", + "GROUP", "LIST", "LET_BLOCK", "ARRAY_COMP", "GENERATOR", "COMP_TAIL", + + // Terminals. + "IDENTIFIER", "NUMBER", "STRING", "REGEXP", + + // Keywords. + "break", + "case", "catch", "const", "continue", + "debugger", "default", "delete", "do", + "else", + "false", "finally", "for", "function", + "if", "in", "instanceof", + "let", + "new", "null", + "return", + "switch", + "this", "throw", "true", "try", "typeof", + "var", "void", + "yield", + "while", "with", +]; + +var statementStartTokens = [ + "break", + "const", "continue", + "debugger", "do", + "for", + "if", + "return", + "switch", + "throw", "try", + "var", + "yield", + "while", "with", +]; + +// Operator and punctuator mapping from token to tree node type name. +// NB: because the lexer doesn't backtrack, all token prefixes must themselves +// be valid tokens (e.g. !== is acceptable because its prefixes are the valid +// tokens != and !). +var opTypeNames = { + '\n': "NEWLINE", + ';': "SEMICOLON", + ',': "COMMA", + '?': "HOOK", + ':': "COLON", + '||': "OR", + '&&': "AND", + '|': "BITWISE_OR", + '^': "BITWISE_XOR", + '&': "BITWISE_AND", + '===': "STRICT_EQ", + '==': "EQ", + '=': "ASSIGN", + '!==': "STRICT_NE", + '!=': "NE", + '<<': "LSH", + '<=': "LE", + '<': "LT", + '>>>': "URSH", + '>>': "RSH", + '>=': "GE", + '>': "GT", + '++': "INCREMENT", + '--': "DECREMENT", + '+': "PLUS", + '-': "MINUS", + '*': "MUL", + '/': "DIV", + '%': "MOD", + '!': "NOT", + '~': "BITWISE_NOT", + '.': "DOT", + '[': "LEFT_BRACKET", + ']': "RIGHT_BRACKET", + '{': "LEFT_CURLY", + '}': "RIGHT_CURLY", + '(': "LEFT_PAREN", + ')': "RIGHT_PAREN" +}; + +// Hash of keyword identifier to tokens index. NB: we must null __proto__ to +// avoid toString, etc. namespace pollution. +var keywords = {__proto__: null}; + +// Define const END, etc., based on the token names. Also map name to index. +var tokenIds = {}; + +// Building up a string to be eval'd in different contexts. +var consts = "const "; +for (var i = 0, j = tokens.length; i < j; i++) { + if (i > 0) + consts += ", "; + var t = tokens[i]; + var name; + if (/^[a-z]/.test(t)) { + name = t.toUpperCase(); + keywords[t] = i; + } else { + name = (/^\W/.test(t) ? opTypeNames[t] : t); + } + consts += name + " = " + i; + tokenIds[name] = i; + tokens[t] = i; +} +consts += ";"; + +var isStatementStartCode = {__proto__: null}; +for (i = 0, j = statementStartTokens.length; i < j; i++) + isStatementStartCode[keywords[statementStartTokens[i]]] = true; + +// Map assignment operators to their indexes in the tokens array. +var assignOps = ['|', '^', '&', '<<', '>>', '>>>', '+', '-', '*', '/', '%']; + +for (i = 0, j = assignOps.length; i < j; i++) { + t = assignOps[i]; + assignOps[t] = tokens[t]; +} + +function defineGetter(obj, prop, fn, dontDelete, dontEnum) { + Object.defineProperty(obj, prop, + { get: fn, configurable: !dontDelete, enumerable: !dontEnum }); +} + +function defineProperty(obj, prop, val, dontDelete, readOnly, dontEnum) { + Object.defineProperty(obj, prop, + { value: val, writable: !readOnly, configurable: !dontDelete, + enumerable: !dontEnum }); +} + +// Returns true if fn is a native function. (Note: SpiderMonkey specific.) +function isNativeCode(fn) { + // Relies on the toString method to identify native code. + return ((typeof fn) === "function") && fn.toString().match(/\[native code\]/); +} + +function getPropertyDescriptor(obj, name) { + while (obj) { + if (({}).hasOwnProperty.call(obj, name)) + return Object.getOwnPropertyDescriptor(obj, name); + obj = Object.getPrototypeOf(obj); + } +} + +function getOwnProperties(obj) { + var map = {}; + for (var name in Object.getOwnPropertyNames(obj)) + map[name] = Object.getOwnPropertyDescriptor(obj, name); + return map; +} + +function makePassthruHandler(obj) { + // Handler copied from + // http://wiki.ecmascript.org/doku.php?id=harmony:proxies&s=proxy%20object#examplea_no-op_forwarding_proxy + return { + getOwnPropertyDescriptor: function(name) { + var desc = Object.getOwnPropertyDescriptor(obj, name); + + // a trapping proxy's properties must always be configurable + desc.configurable = true; + return desc; + }, + getPropertyDescriptor: function(name) { + var desc = getPropertyDescriptor(obj, name); + + // a trapping proxy's properties must always be configurable + desc.configurable = true; + return desc; + }, + getOwnPropertyNames: function() { + return Object.getOwnPropertyNames(obj); + }, + defineProperty: function(name, desc) { + Object.defineProperty(obj, name, desc); + }, + "delete": function(name) { return delete obj[name]; }, + fix: function() { + if (Object.isFrozen(obj)) { + return getOwnProperties(obj); + } + + // As long as obj is not frozen, the proxy won't allow itself to be fixed. + return undefined; // will cause a TypeError to be thrown + }, + + has: function(name) { return name in obj; }, + hasOwn: function(name) { return ({}).hasOwnProperty.call(obj, name); }, + get: function(receiver, name) { return obj[name]; }, + + // bad behavior when set fails in non-strict mode + set: function(receiver, name, val) { obj[name] = val; return true; }, + enumerate: function() { + var result = []; + for (name in obj) { result.push(name); }; + return result; + }, + keys: function() { return Object.keys(obj); } + }; +} + +// default function used when looking for a property in the global object +function noPropFound() { return undefined; } + +var hasOwnProperty = ({}).hasOwnProperty; + +function StringMap() { + this.table = Object.create(null, {}); + this.size = 0; +} + +StringMap.prototype = { + has: function(x) { return hasOwnProperty.call(this.table, x); }, + set: function(x, v) { + if (!hasOwnProperty.call(this.table, x)) + this.size++; + this.table[x] = v; + }, + get: function(x) { return this.table[x]; }, + getDef: function(x, thunk) { + if (!hasOwnProperty.call(this.table, x)) { + this.size++; + this.table[x] = thunk(); + } + return this.table[x]; + }, + forEach: function(f) { + var table = this.table; + for (var key in table) + f.call(this, key, table[key]); + }, + toString: function() { return "[object StringMap]" } +}; + +// non-destructive stack +function Stack(elts) { + this.elts = elts || null; +} + +Stack.prototype = { + push: function(x) { + return new Stack({ top: x, rest: this.elts }); + }, + top: function() { + if (!this.elts) + throw new Error("empty stack"); + return this.elts.top; + }, + isEmpty: function() { + return this.top === null; + }, + find: function(test) { + for (var elts = this.elts; elts; elts = elts.rest) { + if (test(elts.top)) + return elts.top; + } + return null; + }, + has: function(x) { + return Boolean(this.find(function(elt) { return elt === x })); + }, + forEach: function(f) { + for (var elts = this.elts; elts; elts = elts.rest) { + f(elts.top); + } + } +}; + +exports.tokens = tokens; +exports.opTypeNames = opTypeNames; +exports.keywords = keywords; +exports.isStatementStartCode = isStatementStartCode; +exports.tokenIds = tokenIds; +exports.consts = consts; +exports.assignOps = assignOps; +exports.defineGetter = defineGetter; +exports.defineProperty = defineProperty; +exports.isNativeCode = isNativeCode; +exports.makePassthruHandler = makePassthruHandler; +exports.noPropFound = noPropFound; +exports.StringMap = StringMap; +exports.Stack = Stack; + +}); \ No newline at end of file diff --git a/lib/ace/narcissus/jslex.js b/lib/ace/narcissus/jslex.js new file mode 100644 index 00000000..c551792f --- /dev/null +++ b/lib/ace/narcissus/jslex.js @@ -0,0 +1,460 @@ +/* vim: set sw=4 ts=4 et tw=78: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Narcissus JavaScript engine. + * + * The Initial Developer of the Original Code is + * Brendan Eich . + * Portions created by the Initial Developer are Copyright (C) 2004 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Tom Austin + * Brendan Eich + * Shu-Yu Guo + * Dave Herman + * Dimitris Vardoulakis + * Patrick Walton + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * Narcissus - JS implemented in JS. + * + * Lexical scanner. + */ + +define(function(require, exports, module) { + +var definitions = require("ace/narcissus/jsdefs"); + +// Set constants in the local scope. +eval(definitions.consts); + +// Build up a trie of operator tokens. +var opTokens = {}; +for (var op in definitions.opTypeNames) { + if (op === '\n' || op === '.') + continue; + + var node = opTokens; + for (var i = 0; i < op.length; i++) { + var ch = op[i]; + if (!(ch in node)) + node[ch] = {}; + node = node[ch]; + node.op = op; + } +} + +/* + * Tokenizer :: (source, filename, line number) -> Tokenizer + */ +function Tokenizer(s, f, l) { + this.cursor = 0; + this.source = String(s); + this.tokens = []; + this.tokenIndex = 0; + this.lookahead = 0; + this.scanNewlines = false; + this.unexpectedEOF = false; + this.filename = f || ""; + this.lineno = l || 1; +} + +Tokenizer.prototype = { + get done() { + // We need to set scanOperand to true here because the first thing + // might be a regexp. + return this.peek(true) === END; + }, + + get token() { + return this.tokens[this.tokenIndex]; + }, + + match: function (tt, scanOperand) { + return this.get(scanOperand) === tt || this.unget(); + }, + + mustMatch: function (tt) { + if (!this.match(tt)) { + throw this.newSyntaxError("Missing " + + definitions.tokens[tt].toLowerCase()); + } + return this.token; + }, + + peek: function (scanOperand) { + var tt, next; + if (this.lookahead) { + next = this.tokens[(this.tokenIndex + this.lookahead) & 3]; + tt = (this.scanNewlines && next.lineno !== this.lineno) + ? NEWLINE + : next.type; + } else { + tt = this.get(scanOperand); + this.unget(); + } + return tt; + }, + + peekOnSameLine: function (scanOperand) { + this.scanNewlines = true; + var tt = this.peek(scanOperand); + this.scanNewlines = false; + return tt; + }, + + // Eat comments and whitespace. + skip: function () { + var input = this.source; + for (;;) { + var ch = input[this.cursor++]; + var next = input[this.cursor]; + if (ch === '\n' && !this.scanNewlines) { + this.lineno++; + } else if (ch === '/' && next === '*') { + this.cursor++; + for (;;) { + ch = input[this.cursor++]; + if (ch === undefined) + throw this.newSyntaxError("Unterminated comment"); + + if (ch === '*') { + next = input[this.cursor]; + if (next === '/') { + this.cursor++; + break; + } + } else if (ch === '\n') { + this.lineno++; + } + } + } else if (ch === '/' && next === '/') { + this.cursor++; + for (;;) { + ch = input[this.cursor++]; + if (ch === undefined) + return; + + if (ch === '\n') { + this.lineno++; + break; + } + } + } else if (ch !== ' ' && ch !== '\t') { + this.cursor--; + return; + } + } + }, + + // Lex the exponential part of a number, if present. Return true iff an + // exponential part was found. + lexExponent: function() { + var input = this.source; + var next = input[this.cursor]; + if (next === 'e' || next === 'E') { + this.cursor++; + ch = input[this.cursor++]; + if (ch === '+' || ch === '-') + ch = input[this.cursor++]; + + if (ch < '0' || ch > '9') + throw this.newSyntaxError("Missing exponent"); + + do { + ch = input[this.cursor++]; + } while (ch >= '0' && ch <= '9'); + this.cursor--; + + return true; + } + + return false; + }, + + lexZeroNumber: function (ch) { + var token = this.token, input = this.source; + token.type = NUMBER; + + ch = input[this.cursor++]; + if (ch === '.') { + do { + ch = input[this.cursor++]; + } while (ch >= '0' && ch <= '9'); + this.cursor--; + + this.lexExponent(); + token.value = parseFloat(token.start, this.cursor); + } else if (ch === 'x' || ch === 'X') { + do { + ch = input[this.cursor++]; + } while ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || + (ch >= 'A' && ch <= 'F')); + this.cursor--; + + token.value = parseInt(input.substring(token.start, this.cursor)); + } else if (ch >= '0' && ch <= '7') { + do { + ch = input[this.cursor++]; + } while (ch >= '0' && ch <= '7'); + this.cursor--; + + token.value = parseInt(input.substring(token.start, this.cursor)); + } else { + this.cursor--; + this.lexExponent(); // 0E1, &c. + token.value = 0; + } + }, + + lexNumber: function (ch) { + var token = this.token, input = this.source; + token.type = NUMBER; + + var floating = false; + do { + ch = input[this.cursor++]; + if (ch === '.' && !floating) { + floating = true; + ch = input[this.cursor++]; + } + } while (ch >= '0' && ch <= '9'); + + this.cursor--; + + var exponent = this.lexExponent(); + floating = floating || exponent; + + var str = input.substring(token.start, this.cursor); + token.value = floating ? parseFloat(str) : parseInt(str); + }, + + lexDot: function (ch) { + var token = this.token, input = this.source; + var next = input[this.cursor]; + if (next >= '0' && next <= '9') { + do { + ch = input[this.cursor++]; + } while (ch >= '0' && ch <= '9'); + this.cursor--; + + this.lexExponent(); + + token.type = NUMBER; + token.value = parseFloat(token.start, this.cursor); + } else { + token.type = DOT; + token.assignOp = null; + token.value = '.'; + } + }, + + lexString: function (ch) { + var token = this.token, input = this.source; + token.type = STRING; + + var hasEscapes = false; + var delim = ch; + while ((ch = input[this.cursor++]) !== delim) { + if (this.cursor == input.length) + throw this.newSyntaxError("Unterminated string literal"); + if (ch === '\\') { + hasEscapes = true; + if (++this.cursor == input.length) + throw this.newSyntaxError("Unterminated string literal"); + } + } + + token.value = hasEscapes + ? eval(input.substring(token.start, this.cursor)) + : input.substring(token.start + 1, this.cursor - 1); + }, + + lexRegExp: function (ch) { + var token = this.token, input = this.source; + token.type = REGEXP; + + do { + ch = input[this.cursor++]; + if (ch === '\\') { + this.cursor++; + } else if (ch === '[') { + do { + if (ch === undefined) + throw this.newSyntaxError("Unterminated character class"); + + if (ch === '\\') + this.cursor++; + + ch = input[this.cursor++]; + } while (ch !== ']'); + } else if (ch === undefined) { + throw this.newSyntaxError("Unterminated regex"); + } + } while (ch !== '/'); + + do { + ch = input[this.cursor++]; + } while (ch >= 'a' && ch <= 'z'); + + this.cursor--; + + token.value = eval(input.substring(token.start, this.cursor)); + }, + + lexOp: function (ch) { + var token = this.token, input = this.source; + + // A bit ugly, but it seems wasteful to write a trie lookup routine + // for only 3 characters... + var node = opTokens[ch]; + var next = input[this.cursor]; + if (next in node) { + node = node[next]; + this.cursor++; + next = input[this.cursor]; + if (next in node) { + node = node[next]; + this.cursor++; + next = input[this.cursor]; + } + } + + var op = node.op; + if (definitions.assignOps[op] && input[this.cursor] === '=') { + this.cursor++; + token.type = ASSIGN; + token.assignOp = definitions.tokenIds[definitions.opTypeNames[op]]; + op += '='; + } else { + token.type = definitions.tokenIds[definitions.opTypeNames[op]]; + token.assignOp = null; + } + + token.value = op; + }, + + // FIXME: Unicode escape sequences + // FIXME: Unicode identifiers + lexIdent: function (ch) { + var token = this.token, input = this.source; + + do { + ch = input[this.cursor++]; + } while ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9') || ch === '$' || ch === '_'); + + this.cursor--; // Put the non-word character back. + + var id = input.substring(token.start, this.cursor); + token.type = definitions.keywords[id] || IDENTIFIER; + token.value = id; + }, + + /* + * Tokenizer.get :: void -> token type + * + * Consume input *only* if there is no lookahead. + * Dispatch to the appropriate lexing function depending on the input. + */ + get: function (scanOperand) { + var token; + while (this.lookahead) { + --this.lookahead; + this.tokenIndex = (this.tokenIndex + 1) & 3; + token = this.tokens[this.tokenIndex]; + if (token.type !== NEWLINE || this.scanNewlines) + return token.type; + } + + this.skip(); + + this.tokenIndex = (this.tokenIndex + 1) & 3; + token = this.tokens[this.tokenIndex]; + if (!token) + this.tokens[this.tokenIndex] = token = {}; + + var input = this.source; + if (this.cursor === input.length) + return token.type = END; + + token.start = this.cursor; + token.lineno = this.lineno; + + var ch = input[this.cursor++]; + if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch === '$' || ch === '_') { + this.lexIdent(ch); + } else if (scanOperand && ch === '/') { + this.lexRegExp(ch); + } else if (ch in opTokens) { + this.lexOp(ch); + } else if (ch === '.') { + this.lexDot(ch); + } else if (ch >= '1' && ch <= '9') { + this.lexNumber(ch); + } else if (ch === '0') { + this.lexZeroNumber(ch); + } else if (ch === '"' || ch === "'") { + this.lexString(ch); + } else if (this.scanNewlines && ch === '\n') { + token.type = NEWLINE; + token.value = '\n'; + this.lineno++; + } else { + throw this.newSyntaxError("Illegal token"); + } + + token.end = this.cursor; + return token.type; + }, + + /* + * Tokenizer.unget :: void -> undefined + * + * Match depends on unget returning undefined. + */ + unget: function () { + if (++this.lookahead === 4) throw "PANIC: too much lookahead!"; + this.tokenIndex = (this.tokenIndex - 1) & 3; + }, + + newSyntaxError: function (m) { + var e = new SyntaxError(m, this.filename, this.lineno); + e.source = this.source; + e.lineno = this.lineno; + e.cursor = this.lookahead + ? this.tokens[(this.tokenIndex + this.lookahead) & 3].start + : this.cursor; + return e; + }, +}; + +exports.Tokenizer = Tokenizer; + +}); \ No newline at end of file diff --git a/lib/ace/narcissus/jsparse.js b/lib/ace/narcissus/jsparse.js new file mode 100644 index 00000000..46b85ff3 --- /dev/null +++ b/lib/ace/narcissus/jsparse.js @@ -0,0 +1,1432 @@ +/* -*- Mode: JS; tab-width: 4; indent-tabs-mode: nil; -*- + * vim: set sw=4 ts=4 et tw=78: + * ***** BEGIN LICENSE BLOCK ***** + * + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Narcissus JavaScript engine. + * + * The Initial Developer of the Original Code is + * Brendan Eich . + * Portions created by the Initial Developer are Copyright (C) 2004 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Tom Austin + * Brendan Eich + * Shu-Yu Guo + * Dave Herman + * Dimitris Vardoulakis + * Patrick Walton + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * Narcissus - JS implemented in JS. + * + * Parser. + */ + +define(function(require, exports, module) { + +var lexer = require("ace/narcissus/jslex"); +var definitions = require("ace/narcissus/jsdefs"); + +const StringMap = definitions.StringMap; +const Stack = definitions.Stack; + +// Set constants in the local scope. +eval(definitions.consts); + +/* + * pushDestructuringVarDecls :: (node, hoisting node) -> void + * + * Recursively add all destructured declarations to varDecls. + */ +function pushDestructuringVarDecls(n, s) { + for (var i in n) { + var sub = n[i]; + if (sub.type === IDENTIFIER) { + s.varDecls.push(sub); + } else { + pushDestructuringVarDecls(sub, s); + } + } +} + +// NESTING_TOP: top-level +// NESTING_SHALLOW: nested within static forms such as { ... } or labeled statement +// NESTING_DEEP: nested within dynamic forms such as if, loops, etc. +const NESTING_TOP = 0, NESTING_SHALLOW = 1, NESTING_DEEP = 2; + +function StaticContext(parentScript, parentBlock, inFunction, inForLoopInit, nesting) { + this.parentScript = parentScript; + this.parentBlock = parentBlock; + this.inFunction = inFunction; + this.inForLoopInit = inForLoopInit; + this.nesting = nesting; + this.allLabels = new Stack(); + this.currentLabels = new Stack(); + this.labeledTargets = new Stack(); + this.defaultTarget = null; + definitions.options.ecma3OnlyMode && (this.ecma3OnlyMode = true); + definitions.options.parenFreeMode && (this.parenFreeMode = true); +} + +StaticContext.prototype = { + ecma3OnlyMode: false, + parenFreeMode: false, + // non-destructive update via prototype extension + update: function(ext) { + var desc = {}; + for (var key in ext) { + desc[key] = { + value: ext[key], + writable: true, + enumerable: true, + configurable: true + } + } + return Object.create(this, desc); + }, + pushLabel: function(label) { + return this.update({ currentLabels: this.currentLabels.push(label), + allLabels: this.allLabels.push(label) }); + }, + pushTarget: function(target) { + var isDefaultTarget = target.isLoop || target.type === SWITCH; + + if (this.currentLabels.isEmpty()) { + return isDefaultTarget + ? this.update({ defaultTarget: target }) + : this; + } + + target.labels = new StringMap(); + this.currentLabels.forEach(function(label) { + target.labels.set(label, true); + }); + return this.update({ currentLabels: new Stack(), + labeledTargets: this.labeledTargets.push(target), + defaultTarget: isDefaultTarget + ? target + : this.defaultTarget }); + }, + nest: function(atLeast) { + var nesting = Math.max(this.nesting, atLeast); + return (nesting !== this.nesting) + ? this.update({ nesting: nesting }) + : this; + } +}; + +/* + * Script :: (tokenizer, boolean) -> node + * + * Parses the toplevel and function bodies. + */ +function Script(t, inFunction) { + var n = new Node(t, scriptInit()); + var x = new StaticContext(n, n, inFunction, false, NESTING_TOP); + Statements(t, x, n); + return n; +} + +// We extend Array slightly with a top-of-stack method. +definitions.defineProperty(Array.prototype, "top", + function() { + return this.length && this[this.length-1]; + }, false, false, true); + +/* + * Node :: (tokenizer, optional init object) -> node + */ +function Node(t, init) { + var token = t.token; + if (token) { + // If init.type exists it will override token.type. + this.type = token.type; + this.value = token.value; + this.lineno = token.lineno; + + // Start and end are file positions for error handling. + this.start = token.start; + this.end = token.end; + } else { + this.lineno = t.lineno; + } + + // Node uses a tokenizer for debugging (getSource, filename getter). + this.tokenizer = t; + this.children = []; + + for (var prop in init) + this[prop] = init[prop]; +} + +var Np = Node.prototype = {}; +Np.constructor = Node; +Np.toSource = Object.prototype.toSource; + +// Always use push to add operands to an expression, to update start and end. +Np.push = function (kid) { + // kid can be null e.g. [1, , 2]. + if (kid !== null) { + if (kid.start < this.start) + this.start = kid.start; + if (this.end < kid.end) + this.end = kid.end; + } + return this.children.push(kid); +} + +Node.indentLevel = 0; + +function tokenString(tt) { + var t = definitions.tokens[tt]; + return /^\W/.test(t) ? definitions.opTypeNames[t] : t.toUpperCase(); +} + +Np.toString = function () { + var a = []; + for (var i in this) { + if (this.hasOwnProperty(i) && i !== 'type' && i !== 'target') + a.push({id: i, value: this[i]}); + } + a.sort(function (a,b) { return (a.id < b.id) ? -1 : 1; }); + const INDENTATION = " "; + var n = ++Node.indentLevel; + var s = "{\n" + INDENTATION.repeat(n) + "type: " + tokenString(this.type); + for (i = 0; i < a.length; i++) + s += ",\n" + INDENTATION.repeat(n) + a[i].id + ": " + a[i].value; + n = --Node.indentLevel; + s += "\n" + INDENTATION.repeat(n) + "}"; + return s; +} + +Np.getSource = function () { + return this.tokenizer.source.slice(this.start, this.end); +}; + +/* + * Helper init objects for common nodes. + */ + +const LOOP_INIT = { isLoop: true }; + +function blockInit() { + return { type: BLOCK, varDecls: [] }; +} + +function scriptInit() { + return { type: SCRIPT, + funDecls: [], + varDecls: [], + modDecls: [], + impDecls: [], + expDecls: [], + loadDeps: [], + hasEmptyReturn: false, + hasReturnWithValue: false, + isGenerator: false }; +} + +definitions.defineGetter(Np, "filename", + function() { + return this.tokenizer.filename; + }); + +definitions.defineProperty(String.prototype, "repeat", + function(n) { + var s = "", t = this + s; + while (--n >= 0) + s += t; + return s; + }, false, false, true); + +function MaybeLeftParen(t, x) { + if (x.parenFreeMode) + return t.match(LEFT_PAREN) ? LEFT_PAREN : END; + return t.mustMatch(LEFT_PAREN).type; +} + +function MaybeRightParen(t, p) { + if (p === LEFT_PAREN) + t.mustMatch(RIGHT_PAREN); +} + +/* + * Statements :: (tokenizer, compiler context, node) -> void + * + * Parses a sequence of Statements. + */ +function Statements(t, x, n) { + try { + while (!t.done && t.peek(true) !== RIGHT_CURLY) + n.push(Statement(t, x)); + } catch (e) { + if (t.done) + t.unexpectedEOF = true; + throw e; + } +} + +function Block(t, x) { + t.mustMatch(LEFT_CURLY); + var n = new Node(t, blockInit()); + Statements(t, x.update({ parentBlock: n }).pushTarget(n), n); + t.mustMatch(RIGHT_CURLY); + return n; +} + +const DECLARED_FORM = 0, EXPRESSED_FORM = 1, STATEMENT_FORM = 2; + +/* + * Statement :: (tokenizer, compiler context) -> node + * + * Parses a Statement. + */ +function Statement(t, x) { + var i, label, n, n2, p, c, ss, tt = t.get(true), tt2, x2, x3; + + // Cases for statements ending in a right curly return early, avoiding the + // common semicolon insertion magic after this switch. + switch (tt) { + case FUNCTION: + // DECLARED_FORM extends funDecls of x, STATEMENT_FORM doesn't. + return FunctionDefinition(t, x, true, + (x.nesting !== NESTING_TOP) + ? STATEMENT_FORM + : DECLARED_FORM); + + case LEFT_CURLY: + n = new Node(t, blockInit()); + Statements(t, x.update({ parentBlock: n }).pushTarget(n).nest(NESTING_SHALLOW), n); + t.mustMatch(RIGHT_CURLY); + return n; + + case IF: + n = new Node(t); + n.condition = HeadExpression(t, x); + x2 = x.pushTarget(n).nest(NESTING_DEEP); + n.thenPart = Statement(t, x2); + n.elsePart = t.match(ELSE) ? Statement(t, x2) : null; + return n; + + case SWITCH: + // This allows CASEs after a DEFAULT, which is in the standard. + n = new Node(t, { cases: [], defaultIndex: -1 }); + n.discriminant = HeadExpression(t, x); + x2 = x.pushTarget(n).nest(NESTING_DEEP); + t.mustMatch(LEFT_CURLY); + while ((tt = t.get()) !== RIGHT_CURLY) { + switch (tt) { + case DEFAULT: + if (n.defaultIndex >= 0) + throw t.newSyntaxError("More than one switch default"); + // FALL THROUGH + case CASE: + n2 = new Node(t); + if (tt === DEFAULT) + n.defaultIndex = n.cases.length; + else + n2.caseLabel = Expression(t, x2, COLON); + break; + + default: + throw t.newSyntaxError("Invalid switch case"); + } + t.mustMatch(COLON); + n2.statements = new Node(t, blockInit()); + while ((tt=t.peek(true)) !== CASE && tt !== DEFAULT && + tt !== RIGHT_CURLY) + n2.statements.push(Statement(t, x2)); + n.cases.push(n2); + } + return n; + + case FOR: + n = new Node(t, LOOP_INIT); + if (t.match(IDENTIFIER)) { + if (t.token.value === "each") + n.isEach = true; + else + t.unget(); + } + if (!x.parenFreeMode) + t.mustMatch(LEFT_PAREN); + x2 = x.pushTarget(n).nest(NESTING_DEEP); + x3 = x.update({ inForLoopInit: true }); + if ((tt = t.peek()) !== SEMICOLON) { + if (tt === VAR || tt === CONST) { + t.get(); + n2 = Variables(t, x3); + } else if (tt === LET) { + t.get(); + if (t.peek() === LEFT_PAREN) { + n2 = LetBlock(t, x3, false); + } else { + // Let in for head, we need to add an implicit block + // around the rest of the for. + x3.parentBlock = n; + n.varDecls = []; + n2 = Variables(t, x3); + } + } else { + n2 = Expression(t, x3); + } + } + if (n2 && t.match(IN)) { + n.type = FOR_IN; + n.object = Expression(t, x3); + if (n2.type === VAR || n2.type === LET) { + c = n2.children; + + // Destructuring turns one decl into multiples, so either + // there must be only one destructuring or only one + // decl. + if (c.length !== 1 && n2.destructurings.length !== 1) { + throw new SyntaxError("Invalid for..in left-hand side", + t.filename, n2.lineno); + } + if (n2.destructurings.length > 0) { + n.iterator = n2.destructurings[0]; + } else { + n.iterator = c[0]; + } + n.varDecl = n2; + } else { + if (n2.type === ARRAY_INIT || n2.type === OBJECT_INIT) { + n2.destructuredNames = checkDestructuring(t, x3, n2); + } + n.iterator = n2; + } + } else { + n.setup = n2; + t.mustMatch(SEMICOLON); + if (n.isEach) + throw t.newSyntaxError("Invalid for each..in loop"); + n.condition = (t.peek() === SEMICOLON) + ? null + : Expression(t, x3); + t.mustMatch(SEMICOLON); + tt2 = t.peek(); + n.update = (x.parenFreeMode + ? tt2 === LEFT_CURLY || definitions.isStatementStartCode[tt2] + : tt2 === RIGHT_PAREN) + ? null + : Expression(t, x3); + } + if (!x.parenFreeMode) + t.mustMatch(RIGHT_PAREN); + n.body = Statement(t, x2); + return n; + + case WHILE: + n = new Node(t, { isLoop: true }); + n.condition = HeadExpression(t, x); + n.body = Statement(t, x.pushTarget(n).nest(NESTING_DEEP)); + return n; + + case DO: + n = new Node(t, { isLoop: true }); + n.body = Statement(t, x.pushTarget(n).nest(NESTING_DEEP)); + t.mustMatch(WHILE); + n.condition = HeadExpression(t, x); + if (!x.ecmaStrictMode) { + //