From 3cedb3bef6937a09fe7f016c89d7c7e0fb77a2b0 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Sat, 15 Jan 2011 17:31:19 +0100 Subject: [PATCH 01/29] add old worker experiments --- experiments/worker.html | 33 ++++++++ experiments/worker.js | 3 + lib/ace/WorkerTokenizer.js | 91 +++++++++++++++++++++ lib/ace/worker.js | 163 +++++++++++++++++++++++++++++++++++++ 4 files changed, 290 insertions(+) create mode 100644 experiments/worker.html create mode 100644 experiments/worker.js create mode 100644 lib/ace/WorkerTokenizer.js create mode 100644 lib/ace/worker.js 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/WorkerTokenizer.js b/lib/ace/WorkerTokenizer.js new file mode 100644 index 00000000..743f9936 --- /dev/null +++ b/lib/ace/WorkerTokenizer.js @@ -0,0 +1,91 @@ +/** + * Ajax.org Code Editor (ACE) + * + * @copyright 2010, Ajax.org Services B.V. + * @license LGPLv3 + * @author Fabian Jakobs + */ +require.def("ace/WorkerTokenizer", ["ace/lib/oop", "ace/MEventEmitter"], function(oop, MEventEmitter) { + +var WorkerTokenizer = function(tokenizer) { + var workerUrl = require.nameToUrl("ace/worker", null, "_"); + this.$worker = new Worker(workerUrl); + + this.callbackId = 1; + this.callbacks = {}; + + var _self = this; + this.$worker.onerror = function(e) { + throw e; + }; + this.$worker.onmessage = function(e) { + var msg = e.data; + //console.log("rec", msg) + switch(msg.type) { + case "log": + console.log(msg.data); + break; + + case "event": + _self.$dispatchEvent(msg.name, {data: msg.data}); + break; + + case "call": + var callback = _self.callbacks[msg.id]; + if (callback) { + callback(msg.data); + delete _self.callbacks[msg.id]; + } + break; + } + }; +}; + +(function(){ + + oop.implement(this, MEventEmitter); + + this.$send = function(cmd, args) { + this.$worker.postMessage({command: cmd, args: args}); + }; + + this.$call = function(cmd, args, callback) { + var id = this.callbackId++; + this.callbacks[id] = callback; + args.push(id); + this.$send(cmd, args); + }; + + this.setTokenizer = function(tokenizer) { + this.$send("setRules", ["ace/mode/JavaScriptHighlightRules"]); + }; + + this.setLines = function(textLines) { + this.lines = textLines; + this.$send("setLines", [textLines]); + }; + + this.start = function(startRow) { + // TODO don't send all lines on each update!! + this.$send("setLines", [this.lines]); + this.$send("start", [startRow]) + }; + + this.stop = function() { + this.$send("stop", []) + }; + + this.getTokens = function(firstRow, lastRow, callback) { + this.$call("getTokens", [firstRow, lastRow], function(tokens) { + callback(tokens) + }); + }; + + this.getState = function(row, callback) { + this.$call("getState", [row], callback); + }; + +}).call(WorkerTokenizer.prototype); + +return WorkerTokenizer; +}); diff --git a/lib/ace/worker.js b/lib/ace/worker.js new file mode 100644 index 00000000..f90819f1 --- /dev/null +++ b/lib/ace/worker.js @@ -0,0 +1,163 @@ +postMessage("Juhu Kinners"); + +var console = { + log: function(msg) { + postMessage({type: "log", data: msg}); + } +}; +var window = { + console: console +}; + +var require = function(name) { + if (require.modules[name]) + return require.modules[name]; + + importScripts(require.baseUrl + "/" + name + ".js"); + return require.modules[name]; +}; + +require.def = function(name, deps, callback) { + if (!callback) { + callback = deps; + deps = []; + } + var modules = deps.map(function(dep) { + return require(dep); + }); + require.modules[name] = callback.apply(this, modules); +}; + +require.baseUrl = ".."; +require.modules = {}; + +var Tokenizer = require("ace/Tokenizer"); + +var bgtokenizer = { + running: false, + textLines: [], + lines: [], + currentLine: 0, + tokenizer: null, + + init: function() { + var self = this; + + this.$worker = function() { + if (!self.running) { return; } + + var workerStart = new Date(); + var startLine = self.currentLine; + var textLines = self.textLines; + + var processedLines = 0; + + while (self.currentLine < textLines.length) { + self.lines[self.currentLine] = self.$tokenizeRows(self.currentLine, self.currentLine)[0]; + self.currentLine++; + + // only check every 5 lines + processedLines += 1; + if ((processedLines % 5 == 0) && (new Date() - workerStart) > 40) { + self.$event("update", {first: startLine, last: self.currentLine-1}); + self.running = setTimeout(self.$worker, 0); + return; + } + } + + self.running = false; + self.$event("update", {first: startLine, last: textLines.length - 1}); + }; + }, + + setRules: function(rules) { + var Rules = require(rules); + this.tokenizer = new Tokenizer(new Rules().getRules()); + this.lines = []; + + this.start(0); + }, + + setLines: function(textLines) { + this.textLines = textLines; + this.lines = []; + + this.stop(); + }, + + start: function(startRow) { + this.currentLine = Math.min(startRow || 0, this.currentLine, + this.textLines.length); + + // remove all cached items below this line + this.lines.splice(this.currentLine, this.lines.length); + + this.stop(); + this.running = setTimeout(this.$worker, 0); + }, + + stop: function() { + if (this.running) + clearTimeout(this.running); + + this.running = false; + }, + + getTokens: function(firstRow, lastRow, callbackId) { + this.$callback(this.$tokenizeRows(firstRow, lastRow), callbackId); + }, + + getState: function(row, callbackId) { + this.$callback(this.$tokenizeRows(row, row)[0].state, callbackId); + }, + + $tokenizeRows: function(firstRow, lastRow) { + var rows = []; + + // determin start state + var state = "start"; + var doCache = false; + if (firstRow > 0 && this.lines[firstRow - 1]) { + state = this.lines[firstRow - 1].state; + doCache = true; + } + + for (var row=firstRow; row<=lastRow; row++) { + if (!this.lines[row]) { + var tokens = this.tokenizer.getLineTokens(this.textLines[row] || "", state); + var state = tokens.state; + rows.push(tokens); + + if (doCache) { + this.lines[row] = tokens; + } + } + else + rows.push(this.lines[row]); + } + return rows; + }, + + $callback: function(data, callbackId) { + postMessage({ + type: "call", + id: callbackId, + data: data + }); + }, + + $event: function(name, data) { + postMessage({ + type: "event", + name: name, + data: data + }); + } +}; + +bgtokenizer.init(); + +var onmessage = function(e) { + var msg = e.data; + bgtokenizer[msg.command].apply(bgtokenizer, msg.args); +}; \ No newline at end of file From 06960222180aaba3f96284ec0c2255e1ee340bb9 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Mon, 17 Jan 2011 08:44:53 +0100 Subject: [PATCH 02/29] add generic worker infrastructure --- demo/startup.js | 5 + lib/ace/WorkerTokenizer.js | 91 ------------------ lib/ace/worker.js | 163 --------------------------------- lib/ace/worker/Demo.js | 16 ++++ lib/ace/worker/WorkerClient.js | 90 ++++++++++++++++++ lib/ace/worker/host.js | 97 ++++++++++++++++++++ 6 files changed, 208 insertions(+), 254 deletions(-) delete mode 100644 lib/ace/WorkerTokenizer.js delete mode 100644 lib/ace/worker.js create mode 100644 lib/ace/worker/Demo.js create mode 100644 lib/ace/worker/WorkerClient.js create mode 100644 lib/ace/worker/host.js diff --git a/demo/startup.js b/demo/startup.js index b2e473a8..7f32c255 100644 --- a/demo/startup.js +++ b/demo/startup.js @@ -58,12 +58,17 @@ 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 WorkerClient = require("ace/worker/WorkerClient").WorkerClient; var docs = {}; docs.js = new Document(document.getElementById("jstext").innerHTML); docs.js.setMode(new JavaScriptMode()); docs.js.setUndoManager(new UndoManager()); + + var worker = new WorkerClient("../..", ["ace", "pilot"], "ace/worker/demo", "Demo"); + docs.css = new Document(document.getElementById("csstext").innerHTML); docs.css.setMode(new CssMode()); diff --git a/lib/ace/WorkerTokenizer.js b/lib/ace/WorkerTokenizer.js deleted file mode 100644 index 743f9936..00000000 --- a/lib/ace/WorkerTokenizer.js +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Ajax.org Code Editor (ACE) - * - * @copyright 2010, Ajax.org Services B.V. - * @license LGPLv3 - * @author Fabian Jakobs - */ -require.def("ace/WorkerTokenizer", ["ace/lib/oop", "ace/MEventEmitter"], function(oop, MEventEmitter) { - -var WorkerTokenizer = function(tokenizer) { - var workerUrl = require.nameToUrl("ace/worker", null, "_"); - this.$worker = new Worker(workerUrl); - - this.callbackId = 1; - this.callbacks = {}; - - var _self = this; - this.$worker.onerror = function(e) { - throw e; - }; - this.$worker.onmessage = function(e) { - var msg = e.data; - //console.log("rec", msg) - switch(msg.type) { - case "log": - console.log(msg.data); - break; - - case "event": - _self.$dispatchEvent(msg.name, {data: msg.data}); - break; - - case "call": - var callback = _self.callbacks[msg.id]; - if (callback) { - callback(msg.data); - delete _self.callbacks[msg.id]; - } - break; - } - }; -}; - -(function(){ - - oop.implement(this, MEventEmitter); - - this.$send = function(cmd, args) { - this.$worker.postMessage({command: cmd, args: args}); - }; - - this.$call = function(cmd, args, callback) { - var id = this.callbackId++; - this.callbacks[id] = callback; - args.push(id); - this.$send(cmd, args); - }; - - this.setTokenizer = function(tokenizer) { - this.$send("setRules", ["ace/mode/JavaScriptHighlightRules"]); - }; - - this.setLines = function(textLines) { - this.lines = textLines; - this.$send("setLines", [textLines]); - }; - - this.start = function(startRow) { - // TODO don't send all lines on each update!! - this.$send("setLines", [this.lines]); - this.$send("start", [startRow]) - }; - - this.stop = function() { - this.$send("stop", []) - }; - - this.getTokens = function(firstRow, lastRow, callback) { - this.$call("getTokens", [firstRow, lastRow], function(tokens) { - callback(tokens) - }); - }; - - this.getState = function(row, callback) { - this.$call("getState", [row], callback); - }; - -}).call(WorkerTokenizer.prototype); - -return WorkerTokenizer; -}); diff --git a/lib/ace/worker.js b/lib/ace/worker.js deleted file mode 100644 index f90819f1..00000000 --- a/lib/ace/worker.js +++ /dev/null @@ -1,163 +0,0 @@ -postMessage("Juhu Kinners"); - -var console = { - log: function(msg) { - postMessage({type: "log", data: msg}); - } -}; -var window = { - console: console -}; - -var require = function(name) { - if (require.modules[name]) - return require.modules[name]; - - importScripts(require.baseUrl + "/" + name + ".js"); - return require.modules[name]; -}; - -require.def = function(name, deps, callback) { - if (!callback) { - callback = deps; - deps = []; - } - var modules = deps.map(function(dep) { - return require(dep); - }); - require.modules[name] = callback.apply(this, modules); -}; - -require.baseUrl = ".."; -require.modules = {}; - -var Tokenizer = require("ace/Tokenizer"); - -var bgtokenizer = { - running: false, - textLines: [], - lines: [], - currentLine: 0, - tokenizer: null, - - init: function() { - var self = this; - - this.$worker = function() { - if (!self.running) { return; } - - var workerStart = new Date(); - var startLine = self.currentLine; - var textLines = self.textLines; - - var processedLines = 0; - - while (self.currentLine < textLines.length) { - self.lines[self.currentLine] = self.$tokenizeRows(self.currentLine, self.currentLine)[0]; - self.currentLine++; - - // only check every 5 lines - processedLines += 1; - if ((processedLines % 5 == 0) && (new Date() - workerStart) > 40) { - self.$event("update", {first: startLine, last: self.currentLine-1}); - self.running = setTimeout(self.$worker, 0); - return; - } - } - - self.running = false; - self.$event("update", {first: startLine, last: textLines.length - 1}); - }; - }, - - setRules: function(rules) { - var Rules = require(rules); - this.tokenizer = new Tokenizer(new Rules().getRules()); - this.lines = []; - - this.start(0); - }, - - setLines: function(textLines) { - this.textLines = textLines; - this.lines = []; - - this.stop(); - }, - - start: function(startRow) { - this.currentLine = Math.min(startRow || 0, this.currentLine, - this.textLines.length); - - // remove all cached items below this line - this.lines.splice(this.currentLine, this.lines.length); - - this.stop(); - this.running = setTimeout(this.$worker, 0); - }, - - stop: function() { - if (this.running) - clearTimeout(this.running); - - this.running = false; - }, - - getTokens: function(firstRow, lastRow, callbackId) { - this.$callback(this.$tokenizeRows(firstRow, lastRow), callbackId); - }, - - getState: function(row, callbackId) { - this.$callback(this.$tokenizeRows(row, row)[0].state, callbackId); - }, - - $tokenizeRows: function(firstRow, lastRow) { - var rows = []; - - // determin start state - var state = "start"; - var doCache = false; - if (firstRow > 0 && this.lines[firstRow - 1]) { - state = this.lines[firstRow - 1].state; - doCache = true; - } - - for (var row=firstRow; row<=lastRow; row++) { - if (!this.lines[row]) { - var tokens = this.tokenizer.getLineTokens(this.textLines[row] || "", state); - var state = tokens.state; - rows.push(tokens); - - if (doCache) { - this.lines[row] = tokens; - } - } - else - rows.push(this.lines[row]); - } - return rows; - }, - - $callback: function(data, callbackId) { - postMessage({ - type: "call", - id: callbackId, - data: data - }); - }, - - $event: function(name, data) { - postMessage({ - type: "event", - name: name, - data: data - }); - } -}; - -bgtokenizer.init(); - -var onmessage = function(e) { - var msg = e.data; - bgtokenizer[msg.command].apply(bgtokenizer, msg.args); -}; \ No newline at end of file diff --git a/lib/ace/worker/Demo.js b/lib/ace/worker/Demo.js new file mode 100644 index 00000000..7d1fd5df --- /dev/null +++ b/lib/ace/worker/Demo.js @@ -0,0 +1,16 @@ + +define(function(require, exports, module) { + +exports.Demo = function(sender) { + this.sender = sender; +} + +(function() { + + this.juhu = function() { + console.log("JUHU") + } + +}).call(exports.Demot.prototype); + +}); \ No newline at end of file diff --git a/lib/ace/worker/WorkerClient.js b/lib/ace/worker/WorkerClient.js new file mode 100644 index 00000000..00a633c6 --- /dev/null +++ b/lib/ace/worker/WorkerClient.js @@ -0,0 +1,90 @@ +/** + * Ajax.org Code Editor (ACE) + * + * @copyright 2010, Ajax.org Services B.V. + * @license LGPLv3 + * @author Fabian Jakobs + */ + +define(function(require, exports, module) { + +var oop = require("pilot/oop"); +var EventEmitter = require("pilot/event_emitter").EventEmitter; + +var WorkerClient = function(baseUrl, topLevelNamespaces, module, clazz) { + + var workerUrl = require.nameToUrl("ace/worker/host", null, "_"); + var worker = this.$worker = new Worker(workerUrl); + + //context = s.contexts[contextName], + //config = context.config; + //debugger; + + console.log(require.nameToUrl("ace", null, "_")) + console.log(require.nameToUrl("pilot", null, "_")) + + var tlns = {}; + for (var i=0; i Date: Mon, 17 Jan 2011 08:45:15 +0100 Subject: [PATCH 03/29] add old experiments --- experiments/capture.html | 45 +++++++++++++++ experiments/cut_copy.html | 105 ++++++++++++++++++++++++++++++++++ experiments/triple_click.html | 34 +++++++++++ 3 files changed, 184 insertions(+) create mode 100644 experiments/capture.html create mode 100644 experiments/cut_copy.html create mode 100644 experiments/triple_click.html 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 +
+ + + + + + + From d2b141b68e184411e4052f883c77f7095c376f3c Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 18 Jan 2011 09:01:22 +0100 Subject: [PATCH 04/29] cleanup worker basics --- demo/startup.js | 2 +- lib/ace/worker/Demo.js | 6 +++--- lib/ace/worker/WorkerClient.js | 14 +++----------- lib/ace/worker/host.js | 17 +++++------------ 4 files changed, 12 insertions(+), 27 deletions(-) diff --git a/demo/startup.js b/demo/startup.js index 7f32c255..41d8321f 100644 --- a/demo/startup.js +++ b/demo/startup.js @@ -68,7 +68,7 @@ exports.launch = function(env) { docs.js.setUndoManager(new UndoManager()); var worker = new WorkerClient("../..", ["ace", "pilot"], "ace/worker/demo", "Demo"); - + worker.send("juhu"); docs.css = new Document(document.getElementById("csstext").innerHTML); docs.css.setMode(new CssMode()); diff --git a/lib/ace/worker/Demo.js b/lib/ace/worker/Demo.js index 7d1fd5df..7d3b0689 100644 --- a/lib/ace/worker/Demo.js +++ b/lib/ace/worker/Demo.js @@ -1,9 +1,9 @@ define(function(require, exports, module) { -exports.Demo = function(sender) { +var Demo = exports.Demo = function(sender) { this.sender = sender; -} +}; (function() { @@ -11,6 +11,6 @@ exports.Demo = function(sender) { console.log("JUHU") } -}).call(exports.Demot.prototype); +}).call(Demo.prototype); }); \ No newline at end of file diff --git a/lib/ace/worker/WorkerClient.js b/lib/ace/worker/WorkerClient.js index 00a633c6..dd18abea 100644 --- a/lib/ace/worker/WorkerClient.js +++ b/lib/ace/worker/WorkerClient.js @@ -11,31 +11,24 @@ define(function(require, exports, module) { var oop = require("pilot/oop"); var EventEmitter = require("pilot/event_emitter").EventEmitter; -var WorkerClient = function(baseUrl, topLevelNamespaces, module, clazz) { +var WorkerClient = function(baseUrl, topLevelNamespaces, module, classname) { + this.callbacks = []; var workerUrl = require.nameToUrl("ace/worker/host", null, "_"); var worker = this.$worker = new Worker(workerUrl); - //context = s.contexts[contextName], - //config = context.config; - //debugger; - - console.log(require.nameToUrl("ace", null, "_")) - console.log(require.nameToUrl("pilot", null, "_")) - var tlns = {}; for (var i=0; i Date: Tue, 18 Jan 2011 09:01:29 +0100 Subject: [PATCH 05/29] add jslint --- lib/ace/worker/jslint.js | 5747 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 5747 insertions(+) create mode 100644 lib/ace/worker/jslint.js diff --git a/lib/ace/worker/jslint.js b/lib/ace/worker/jslint.js new file mode 100644 index 00000000..a36dd16e --- /dev/null +++ b/lib/ace/worker/jslint.js @@ -0,0 +1,5747 @@ +// jslint.js +// 2011-01-09 + +/* +Copyright (c) 2002 Douglas Crockford (www.JSLint.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +/* + JSLINT is a global function. It takes two parameters. + + var myResult = JSLINT(source, option); + + The first parameter is either a string or an array of strings. If it is a + string, it will be split on '\n' or '\r'. If it is an array of strings, it + is assumed that each string represents one line. The source can be a + JavaScript text, or HTML text, or a JSON text, or a CSS text. + + The second parameter is an optional object of options which control the + operation of JSLINT. Most of the options are booleans: They are all + optional and have a default value of false. One of the options, predef, + can be an array of names, which will be used to declare global variables, + or an object whose keys are used as global names, with a boolean value + that determines if they are assignable. + + If it checks out, JSLINT returns true. Otherwise, it returns false. + + If false, you can inspect JSLINT.errors to find out the problems. + JSLINT.errors is an array of objects containing these members: + + { + line : The line (relative to 0) at which the lint was found + character : The character (relative to 0) at which the lint was found + reason : The problem + evidence : The text line in which the problem occurred + raw : The raw message before the details were inserted + a : The first detail + b : The second detail + c : The third detail + d : The fourth detail + } + + If a fatal error was found, a null will be the last element of the + JSLINT.errors array. + + You can request a Function Report, which shows all of the functions + and the parameters and vars that they use. This can be used to find + implied global variables and other problems. The report is in HTML and + can be inserted in an HTML . + + var myReport = JSLINT.report(limited); + + If limited is true, then the report will be limited to only errors. + + You can request a data structure which contains JSLint's results. + + var myData = JSLINT.data(); + + It returns a structure with this form: + + { + errors: [ + { + line: NUMBER, + character: NUMBER, + reason: STRING, + evidence: STRING + } + ], + functions: [ + name: STRING, + line: NUMBER, + last: NUMBER, + param: [ + STRING + ], + closure: [ + STRING + ], + var: [ + STRING + ], + exception: [ + STRING + ], + outer: [ + STRING + ], + unused: [ + STRING + ], + global: [ + STRING + ], + label: [ + STRING + ] + ], + globals: [ + STRING + ], + member: { + STRING: NUMBER + }, + unuseds: [ + { + name: STRING, + line: NUMBER + } + ], + implieds: [ + { + name: STRING, + line: NUMBER + } + ], + urls: [ + STRING + ], + json: BOOLEAN + } + + Empty arrays will not be included. + + You can obtain the parse tree that JSLint constructed while parsing. The + latest tree is kept in JSLINT.tree. A nice stringication can be produced + with + + JSON.stringify(JSLINT.tree, [ + 'value', 'arity', 'name', 'first', + 'second', 'third', 'block', 'else' + ], 4)); + +*/ + +/*jslint + evil: true, nomen: false, onevar: false, regexp: false, strict: true +*/ + +/*members "\b", "\t", "\n", "\f", "\r", "!=", "!==", "\"", "'", "%", + "(begin)", "(breakage)", "(context)", "(end)", "(error)", "(global)", + "(identifier)", "(last)", "(line)", "(loopage)", "(name)", "(onevar)", + "(params)", "(scope)", "(statement)", "(token)", "(verb)", "*", "+", "++", "-", + "--", "\/", "<", "<=", "", ">=", + ADSAFE, ActiveXObject, Array, Boolean, COM, CScript, Canvas, + CustomAnimation, Date, Debug, E, Enumerator, Error, EvalError, + FadeAnimation, Flash, FormField, Frame, Function, HotKey, Image, JSON, + LN10, LN2, LOG10E, LOG2E, MAX_VALUE, MIN_VALUE, Math, MenuItem, + MoveAnimation, NEGATIVE_INFINITY, Number, Object, Option, PI, + POSITIVE_INFINITY, Point, RangeError, Rectangle, ReferenceError, RegExp, + ResizeAnimation, RotateAnimation, SQRT1_2, SQRT2, ScrollBar, String, + Style, SyntaxError, System, Text, TextArea, Timer, TypeError, URIError, + URL, VBArray, WScript, Web, Window, XMLDOM, XMLHttpRequest, "\\", a, + abbr, acronym, activeborder, activecaption, addEventListener, address, + adsafe, alert, aliceblue, all, animator, antiquewhite, appleScript, + applet, apply, approved, appworkspace, aqua, aquamarine, area, + arguments, arity, article, aside, assign, audio, autocomplete, azure, b, + background, "background-attachment", "background-color", + "background-image", "background-position", "background-repeat", base, + bdo, beep, beige, big, bisque, bitwise, black, blanchedalmond, block, + blockquote, blue, blueviolet, blur, body, border, "border-bottom", + "border-bottom-color", "border-bottom-style", "border-bottom-width", + "border-collapse", "border-color", "border-left", "border-left-color", + "border-left-style", "border-left-width", "border-right", + "border-right-color", "border-right-style", "border-right-width", + "border-spacing", "border-style", "border-top", "border-top-color", + "border-top-style", "border-top-width", "border-width", bottom, br, + braille, brown, browser, burlywood, button, buttonface, buttonhighlight, + buttonshadow, buttontext, bytesToUIString, c, cadetblue, call, callee, + caller, canvas, cap, caption, "caption-side", captiontext, case, catch, + center, charAt, charCodeAt, character, chartreuse, chocolate, + chooseColor, chooseFile, chooseFolder, cite, clear, clearInterval, + clearTimeout, clip, close, closeWidget, closed, closure, cm, code, col, + colgroup, color, command, comment, confirm, console, constructor, + content, convertPathToHFS, convertPathToPlatform, coral, cornflowerblue, + cornsilk, "counter-increment", "counter-reset", create, crimson, css, + cursor, cyan, d, darkblue, darkcyan, darkgoldenrod, darkgray, darkgreen, + darkkhaki, darkmagenta, darkolivegreen, darkorange, darkorchid, darkred, + darksalmon, darkseagreen, darkslateblue, darkslategray, darkturquoise, + darkviolet, data, datalist, dd, debug, decodeURI, decodeURIComponent, + deeppink, deepskyblue, default, defaultStatus, defineClass, del, + deserialize, details, devel, dfn, dialog, dimgray, dir, direction, + display, disrupt, div, dl, do, document, dodgerblue, + dt, edition, else, em, embed, embossed, empty, "empty-cells", encodeURI, + encodeURIComponent, entityify, errors, es5, escape, eval, event, + evidence, evil, ex, exception, exec, fieldset, figure, filesystem, + finally, firebrick, first, float, floor, floralwhite, focus, + focusWidget, font, "font-family", "font-size", "font-size-adjust", + "font-stretch", "font-style", "font-variant", "font-weight", footer, + for, forestgreen, forin, form, fragment, frame, frames, frameset, from, + fromCharCode, fuchsia, fud, funct, function, functions, g, gainsboro, + gc, getComputedStyle, ghostwhite, global, globals, gold, goldenrod, + gray, graytext, green, greenyellow, h1, h2, h3, h4, h5, h6, handheld, + hasOwnProperty, head, header, height, help, hgroup, highlight, + highlighttext, history, honeydew, hotpink, hr, "hta:application", html, + i, iTunes, id, identifier, iframe, img, immed, implieds, in, + inactiveborder, inactivecaption, inactivecaptiontext, include, indent, + indexOf, indianred, indigo, infobackground, infotext, init, initial, + input, ins, isAlpha, isApplicationRunning, isArray, isDigit, isFinite, + isNaN, ivory, join, jslint, json, kbd, keygen, keys, khaki, + konfabulatorVersion, label, lang, last, lavender, lavenderblush, + lawngreen, lbp, led, left, legend, lemonchiffon, length, + "letter-spacing", li, lib, lightblue, lightcoral, lightcyan, + lightgoldenrodyellow, lightgreen, lightpink, lightsalmon, lightseagreen, + lightskyblue, lightslategray, lightsteelblue, lightyellow, lime, + limegreen, line, "line-height", linen, link, "list-style", + "list-style-image", "list-style-position", "list-style-type", load, + loadClass, location, log, m, magenta, map, margin, "margin-bottom", + "margin-left", "margin-right", "margin-top", mark, "marker-offset", + maroon, match, "max-height", "max-width", maxerr, maxlen, md5, + mediumaquamarine, mediumblue, mediumorchid, mediumpurple, + mediumseagreen, mediumslateblue, mediumspringgreen, mediumturquoise, + mediumvioletred, member, menu, menutext, message, meta, meter, + midnightblue, "min-height", "min-width", mintcream, mistyrose, mm, + moccasin, moveBy, moveTo, name, nav, navajowhite, navigator, navy, new, + newcap, noframes, nomen, noscript, nud, object, ol, oldlace, olive, + olivedrab, on, onbeforeunload, onblur, onerror, onevar, onfocus, onload, + onresize, onunload, opacity, open, openURL, opener, opera, optgroup, + option, orange, orangered, orchid, outer, outline, "outline-color", + "outline-style", "outline-width", output, overflow, "overflow-x", + "overflow-y", p, padding, "padding-bottom", "padding-left", + "padding-right", "padding-top", "page-break-after", "page-break-before", + palegoldenrod, palegreen, paleturquoise, palevioletred, papayawhip, + param, parent, parseFloat, parseInt, passfail, pc, peachpuff, peru, + pink, play, plum, plusplus, pop, popupMenu, position, powderblue, pre, + predef, preferenceGroups, preferences, print, progress, projection, + prompt, prototype, pt, purple, push, px, q, quit, quotes, random, range, + raw, readFile, readUrl, reason, red, regexp, reloadWidget, + removeEventListener, replace, report, reserved, resizeBy, resizeTo, + resolvePath, resumeUpdates, rhino, right, rosybrown, royalblue, rp, rt, + ruby, runCommand, runCommandInBg, saddlebrown, safe, salmon, samp, + sandybrown, saveAs, savePreferences, screen, script, scroll, scrollBy, + scrollTo, scrollbar, seagreen, seal, search, seashell, second, section, + select, serialize, setInterval, setTimeout, shift, + showWidgetPreferences, sienna, silver, skyblue, slateblue, slategray, + sleep, slice, small, snow, sort, source, span, spawn, speak, speech, + split, springgreen, src, stack, status, steelblue, strict, strong, + style, styleproperty, sub, substr, sup, supplant, suppressUpdates, + switch, sync, system, table, "table-layout", tan, tbody, td, teal, + tellWidget, test, "text-align", "text-decoration", "text-indent", + "text-shadow", "text-transform", textarea, tfoot, th, thead, third, + thistle, threeddarkshadow, threedface, threedhighlight, + threedlightshadow, threedshadow, thru, time, title, toLowerCase, toString, + toUpperCase, toint32, token, tomato, top, tr, tree, tt, tty, turquoise, tv, + type, u, ul, undef, unescape, "unicode-bidi", unused, unwatch, + updateNow, urls, value, valueOf, var, version, "vertical-align", video, + violet, visibility, watch, wheat, while, white, "white-space", + whitesmoke, widget, width, window, windowframe, windows, windowtext, + "word-spacing", "word-wrap", yahooCheckLogin, yahooLogin, yahooLogout, + yellow, yellowgreen, "z-index", "}" +*/ + +// We build the application inside a function so that we produce only a single +// global variable. That function will be invoked immediately, and its return +// value is the JSLINT function itself. That function is also an object that +// can contain data and other functions. + +define(function(require, exports, module) { + +exports.lint = (function () { + "use strict"; + + var adsafe_id, // The widget's ADsafe id. + adsafe_may, // The widget may load approved scripts. + adsafe_went, // ADSAFE.go has been called. + anonname, // The guessed name for anonymous functions. + approved, // ADsafe approved urls. + +// These are operators that should not be used with the ! operator. + + bang = { + '<': true, + '<=': true, + '==': true, + '===': true, + '!==': true, + '!=': true, + '>': true, + '>=': true, + '+': true, + '-': true, + '*': true, + '/': true, + '%': true + }, + +// These are property names that should not be permitted in the safe subset. + + banned = { // the member names that ADsafe prohibits. + 'arguments' : true, + callee : true, + caller : true, + constructor : true, + 'eval' : true, + prototype : true, + stack : true, + unwatch : true, + valueOf : true, + watch : true + }, + + +// These are the JSLint boolean options. + + boolOptions = { + adsafe : true, // if ADsafe should be enforced + bitwise : true, // if bitwise operators should not be allowed + browser : true, // if the standard browser globals should be predefined + cap : true, // if upper case HTML should be allowed + css : true, // if CSS workarounds should be tolerated + debug : true, // if debugger statements should be allowed + devel : true, // if logging should be allowed (console, alert, etc.) + es5 : true, // if ES5 syntax should be allowed + evil : true, // if eval should be allowed + forin : true, // if for in statements must filter + fragment : true, // if HTML fragments should be allowed + newcap : true, // if constructor names must be capitalized + nomen : true, // if names should be checked + on : true, // if HTML event handlers should be allowed + onevar : true, // if only one var statement per function should be allowed + passfail : true, // if the scan should stop on first error + plusplus : true, // if increment/decrement should not be allowed + regexp : true, // if the . should not be allowed in regexp literals + rhino : true, // if the Rhino environment globals should be predefined + undef : true, // if variables should be declared before used + safe : true, // if use of some browser features should be restricted + windows : true, // if MS Windows-specigic globals should be predefined + strict : true, // require the "use strict"; pragma + sub : true, // if all forms of subscript notation are tolerated + white : true, // if strict whitespace rules apply + widget : true // if the Yahoo Widgets globals should be predefined + }, + +// browser contains a set of global names which are commonly provided by a +// web browser environment. + + browser = { + addEventListener: false, + blur : false, + clearInterval : false, + clearTimeout : false, + close : false, + closed : false, + defaultStatus : false, + document : false, + event : false, + focus : false, + frames : false, + getComputedStyle: false, + history : false, + Image : false, + length : false, + location : false, + moveBy : false, + moveTo : false, + name : false, + navigator : false, + onbeforeunload : true, + onblur : true, + onerror : true, + onfocus : true, + onload : true, + onresize : true, + onunload : true, + open : false, + opener : false, + Option : false, + parent : false, + print : false, + removeEventListener: false, + resizeBy : false, + resizeTo : false, + screen : false, + scroll : false, + scrollBy : false, + scrollTo : false, + setInterval : false, + setTimeout : false, + status : false, + top : false, + XMLHttpRequest : false + }, + + cssAttributeData, + cssAny, + + cssColorData = { + "aliceblue" : true, + "antiquewhite" : true, + "aqua" : true, + "aquamarine" : true, + "azure" : true, + "beige" : true, + "bisque" : true, + "black" : true, + "blanchedalmond" : true, + "blue" : true, + "blueviolet" : true, + "brown" : true, + "burlywood" : true, + "cadetblue" : true, + "chartreuse" : true, + "chocolate" : true, + "coral" : true, + "cornflowerblue" : true, + "cornsilk" : true, + "crimson" : true, + "cyan" : true, + "darkblue" : true, + "darkcyan" : true, + "darkgoldenrod" : true, + "darkgray" : true, + "darkgreen" : true, + "darkkhaki" : true, + "darkmagenta" : true, + "darkolivegreen" : true, + "darkorange" : true, + "darkorchid" : true, + "darkred" : true, + "darksalmon" : true, + "darkseagreen" : true, + "darkslateblue" : true, + "darkslategray" : true, + "darkturquoise" : true, + "darkviolet" : true, + "deeppink" : true, + "deepskyblue" : true, + "dimgray" : true, + "dodgerblue" : true, + "firebrick" : true, + "floralwhite" : true, + "forestgreen" : true, + "fuchsia" : true, + "gainsboro" : true, + "ghostwhite" : true, + "gold" : true, + "goldenrod" : true, + "gray" : true, + "green" : true, + "greenyellow" : true, + "honeydew" : true, + "hotpink" : true, + "indianred" : true, + "indigo" : true, + "ivory" : true, + "khaki" : true, + "lavender" : true, + "lavenderblush" : true, + "lawngreen" : true, + "lemonchiffon" : true, + "lightblue" : true, + "lightcoral" : true, + "lightcyan" : true, + "lightgoldenrodyellow" : true, + "lightgreen" : true, + "lightpink" : true, + "lightsalmon" : true, + "lightseagreen" : true, + "lightskyblue" : true, + "lightslategray" : true, + "lightsteelblue" : true, + "lightyellow" : true, + "lime" : true, + "limegreen" : true, + "linen" : true, + "magenta" : true, + "maroon" : true, + "mediumaquamarine" : true, + "mediumblue" : true, + "mediumorchid" : true, + "mediumpurple" : true, + "mediumseagreen" : true, + "mediumslateblue" : true, + "mediumspringgreen" : true, + "mediumturquoise" : true, + "mediumvioletred" : true, + "midnightblue" : true, + "mintcream" : true, + "mistyrose" : true, + "moccasin" : true, + "navajowhite" : true, + "navy" : true, + "oldlace" : true, + "olive" : true, + "olivedrab" : true, + "orange" : true, + "orangered" : true, + "orchid" : true, + "palegoldenrod" : true, + "palegreen" : true, + "paleturquoise" : true, + "palevioletred" : true, + "papayawhip" : true, + "peachpuff" : true, + "peru" : true, + "pink" : true, + "plum" : true, + "powderblue" : true, + "purple" : true, + "red" : true, + "rosybrown" : true, + "royalblue" : true, + "saddlebrown" : true, + "salmon" : true, + "sandybrown" : true, + "seagreen" : true, + "seashell" : true, + "sienna" : true, + "silver" : true, + "skyblue" : true, + "slateblue" : true, + "slategray" : true, + "snow" : true, + "springgreen" : true, + "steelblue" : true, + "tan" : true, + "teal" : true, + "thistle" : true, + "tomato" : true, + "turquoise" : true, + "violet" : true, + "wheat" : true, + "white" : true, + "whitesmoke" : true, + "yellow" : true, + "yellowgreen" : true, + + "activeborder" : true, + "activecaption" : true, + "appworkspace" : true, + "background" : true, + "buttonface" : true, + "buttonhighlight" : true, + "buttonshadow" : true, + "buttontext" : true, + "captiontext" : true, + "graytext" : true, + "highlight" : true, + "highlighttext" : true, + "inactiveborder" : true, + "inactivecaption" : true, + "inactivecaptiontext" : true, + "infobackground" : true, + "infotext" : true, + "menu" : true, + "menutext" : true, + "scrollbar" : true, + "threeddarkshadow" : true, + "threedface" : true, + "threedhighlight" : true, + "threedlightshadow" : true, + "threedshadow" : true, + "window" : true, + "windowframe" : true, + "windowtext" : true + }, + + cssBorderStyle, + cssBreak, + + cssLengthData = { + '%': true, + 'cm': true, + 'em': true, + 'ex': true, + 'in': true, + 'mm': true, + 'pc': true, + 'pt': true, + 'px': true + }, + + cssMedia, + cssOverflow, + + devel = { + alert : false, + confirm : false, + console : false, + Debug : false, + opera : false, + prompt : false + }, + + escapes = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '/' : '\\/', + '\\': '\\\\' + }, + + funct, // The current function + + functionicity = [ + 'closure', 'exception', 'global', 'label', + 'outer', 'unused', 'var' + ], + + functions, // All of the functions + + global, // The global scope + htmltag = { + a: {}, + abbr: {}, + acronym: {}, + address: {}, + applet: {}, + area: {empty: true, parent: ' map '}, + article: {}, + aside: {}, + audio: {}, + b: {}, + base: {empty: true, parent: ' head '}, + bdo: {}, + big: {}, + blockquote: {}, + body: {parent: ' html noframes '}, + br: {empty: true}, + button: {}, + canvas: {parent: ' body p div th td '}, + caption: {parent: ' table '}, + center: {}, + cite: {}, + code: {}, + col: {empty: true, parent: ' table colgroup '}, + colgroup: {parent: ' table '}, + command: {parent: ' menu '}, + datalist: {}, + dd: {parent: ' dl '}, + del: {}, + details: {}, + dialog: {}, + dfn: {}, + dir: {}, + div: {}, + dl: {}, + dt: {parent: ' dl '}, + em: {}, + embed: {}, + fieldset: {}, + figure: {}, + font: {}, + footer: {}, + form: {}, + frame: {empty: true, parent: ' frameset '}, + frameset: {parent: ' html frameset '}, + h1: {}, + h2: {}, + h3: {}, + h4: {}, + h5: {}, + h6: {}, + head: {parent: ' html '}, + header: {}, + hgroup: {}, + hr: {empty: true}, + 'hta:application': + {empty: true, parent: ' head '}, + html: {parent: '*'}, + i: {}, + iframe: {}, + img: {empty: true}, + input: {empty: true}, + ins: {}, + kbd: {}, + keygen: {}, + label: {}, + legend: {parent: ' details fieldset figure '}, + li: {parent: ' dir menu ol ul '}, + link: {empty: true, parent: ' head '}, + map: {}, + mark: {}, + menu: {}, + meta: {empty: true, parent: ' head noframes noscript '}, + meter: {}, + nav: {}, + noframes: {parent: ' html body '}, + noscript: {parent: ' body head noframes '}, + object: {}, + ol: {}, + optgroup: {parent: ' select '}, + option: {parent: ' optgroup select '}, + output: {}, + p: {}, + param: {empty: true, parent: ' applet object '}, + pre: {}, + progress: {}, + q: {}, + rp: {}, + rt: {}, + ruby: {}, + samp: {}, + script: {empty: true, parent: ' body div frame head iframe p pre span '}, + section: {}, + select: {}, + small: {}, + span: {}, + source: {}, + strong: {}, + style: {parent: ' head ', empty: true}, + sub: {}, + sup: {}, + table: {}, + tbody: {parent: ' table '}, + td: {parent: ' tr '}, + textarea: {}, + tfoot: {parent: ' table '}, + th: {parent: ' tr '}, + thead: {parent: ' table '}, + time: {}, + title: {parent: ' head '}, + tr: {parent: ' table tbody thead tfoot '}, + tt: {}, + u: {}, + ul: {}, + 'var': {}, + video: {} + }, + + ids, // HTML ids + implied, // Implied globals + inblock, + jsonmode, + labelled = { + 'do': true, + 'for': true, + 'switch': true, + 'while': true + }, + lines, + lookahead, + member, + membersOnly, + nexttoken, + option, + postscript = { + '(end)': true, + '(error)': true, + '>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/, +// html token + hx = /^\s*(['"=>\/&#]|<(?:\/|\!(?:--)?)?|[a-zA-Z][a-zA-Z0-9_\-:]*|[0-9]+|--)/, +// characters in strings that need escapement + nx = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/, + nxg = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, +// outer html token + ox = /[>&]|<[\/!]?|--/, +// star slash + lx = /\*\/|\/\*/, +// identifier + ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/, +// javascript url + jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i, +// url badness + ux = /&|\+|\u00AD|\.\.|\/\*|%[^;]|base64|url|expression|data|mailto/i, +// style + sx = /^\s*([{:#%.=,>+\[\]@()"';]|\*=?|\$=|\|=|\^=|~=|[a-zA-Z_][a-zA-Z0-9_\-]*|[0-9]+|<\/|\/\*)/, + ssx = /^\s*([@#!"'};:\-%.=,+\[\]()*_]|[a-zA-Z][a-zA-Z0-9._\-]*|\/\*?|\d+(?:\.\d+)?|<\/)/, +// attributes characters + qx = /[^a-zA-Z0-9+\-_\/ ]/, +// query characters for ids + dx = /[\[\]\/\\"'*<>.&:(){}+=#]/, + + rx = { + outer: hx, + html: hx, + style: sx, + styleproperty: ssx + }; + + + function F() {} // Used by Object.create + + function is_own(object, name) { + +// The object.hasOwnProperty method fails when the property under consideration +// is named 'hasOwnProperty'. So we have to use this more convoluted form. + + return Object.prototype.hasOwnProperty.call(object, name); + } + +// Provide critical ES5 functions to ES3. + + if (typeof Array.isArray !== 'function') { + Array.isArray = function (o) { + return Object.prototype.toString.apply(o) === '[object Array]'; + }; + } + + if (typeof Object.create !== 'function') { + Object.create = function (o) { + F.prototype = o; + return new F(); + }; + } + + if (typeof Object.keys !== 'function') { + Object.keys = function (o) { + var a = [], k; + for (k in o) { + if (is_own(o, k)) { + a.push(k); + } + } + return a; + }; + } + +// Non standard methods + + if (typeof String.prototype.entityify !== 'function') { + String.prototype.entityify = function () { + return this + .replace(/&/g, '&') + .replace(//g, '>'); + }; + } + + if (typeof String.prototype.isAlpha !== 'function') { + String.prototype.isAlpha = function () { + return (this >= 'a' && this <= 'z\uffff') || + (this >= 'A' && this <= 'Z\uffff'); + }; + } + + if (typeof String.prototype.isDigit !== 'function') { + String.prototype.isDigit = function () { + return (this >= '0' && this <= '9'); + }; + } + + if (typeof String.prototype.supplant !== 'function') { + String.prototype.supplant = function (o) { + return this.replace(/\{([^{}]*)\}/g, function (a, b) { + var r = o[b]; + return typeof r === 'string' || typeof r === 'number' ? r : a; + }); + }; + } + + if (typeof String.prototype.name !== 'function') { + String.prototype.name = function () { + +// If the string looks like an identifier, then we can return it as is. +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can simply slap some quotes around it. +// Otherwise we must also replace the offending characters with safe +// sequences. + + if (ix.test(this)) { + return this; + } + if (nx.test(this)) { + return '"' + this.replace(nxg, function (a) { + var c = escapes[a]; + if (c) { + return c; + } + return '\\u' + ('0000' + a.charCodeAt().toString(16)).slice(-4); + }) + '"'; + } + return '"' + this + '"'; + }; + } + + + function combine(t, o) { + var n; + for (n in o) { + if (is_own(o, n)) { + t[n] = o[n]; + } + } + } + + function assume() { + if (!option.safe) { + if (option.rhino) { + combine(predefined, rhino); + } + if (option.devel) { + combine(predefined, devel); + } + if (option.browser) { + combine(predefined, browser); + } + if (option.windows) { + combine(predefined, windows); + } + if (option.widget) { + combine(predefined, widget); + } + } + } + + +// Produce an error warning. + + function quit(m, l, ch) { + throw { + name: 'JSLintError', + line: l, + character: ch, + message: m + " (" + Math.floor((l / lines.length) * 100) + + "% scanned)." + }; + } + + function warning(m, t, a, b, c, d) { + var ch, l, w; + t = t || nexttoken; + if (t.id === '(end)') { // `~ + t = token; + } + l = t.line || 0; + ch = t.from || 0; + w = { + id: '(error)', + raw: m, + evidence: lines[l - 1] || '', + line: l, + character: ch, + a: a, + b: b, + c: c, + d: d + }; + w.reason = m.supplant(w); + JSLINT.errors.push(w); + if (option.passfail) { + quit('Stopping. ', l, ch); + } + warnings += 1; + if (warnings >= option.maxerr) { + quit("Too many errors.", l, ch); + } + return w; + } + + function warningAt(m, l, ch, a, b, c, d) { + return warning(m, { + line: l, + from: ch + }, a, b, c, d); + } + + function error(m, t, a, b, c, d) { + var w = warning(m, t, a, b, c, d); + quit("Stopping, unable to continue.", w.line, w.character); + } + + function errorAt(m, l, ch, a, b, c, d) { + return error(m, { + line: l, + from: ch + }, a, b, c, d); + } + + + +// lexical analysis and token construction + + var lex = (function lex() { + var character, from, line, s; + +// Private lex methods + + function nextLine() { + var at; + if (line >= lines.length) { + return false; + } + character = 1; + s = lines[line]; + line += 1; + at = s.search(/ \t/); + if (at >= 0) { + warningAt("Mixed spaces and tabs.", line, at + 1); + } + s = s.replace(/\t/g, tab); + at = s.search(cx); + if (at >= 0) { + warningAt("Unsafe character.", line, at); + } + if (option.maxlen && option.maxlen < s.length) { + warningAt("Line too long.", line, s.length); + } + return true; + } + +// Produce a token object. The token inherits from a syntax symbol. + + function it(type, value) { + var i, t; + if (type === '(color)' || type === '(range)') { + t = {type: type}; + } else if (type === '(punctuator)' || + (type === '(identifier)' && is_own(syntax, value))) { + t = syntax[value] || syntax['(error)']; + } else { + t = syntax[type]; + } + t = Object.create(t); + if (type === '(string)' || type === '(range)') { + if (jx.test(value)) { + warningAt("Script URL.", line, from); + } + } + if (type === '(identifier)') { + t.identifier = true; + if (value === '__iterator__' || value === '__proto__') { + errorAt("Reserved name '{a}'.", + line, from, value); + } else if (option.nomen && + (value.charAt(0) === '_' || + value.charAt(value.length - 1) === '_')) { + warningAt("Unexpected {a} in '{b}'.", line, from, + "dangling '_'", value); + } + } + if (value !== undefined) { + t.value = value; + } + t.line = line; + t.thru = character; + t.from = from; + i = t.id; + prereg = i && + (('(,=:[!&|?{};'.indexOf(i.charAt(i.length - 1)) >= 0) || + i === 'return'); + return t; + } + +// Public lex methods + + return { + init: function (source) { + if (typeof source === 'string') { + lines = source + .replace(/\r\n/g, '\n') + .replace(/\r/g, '\n') + .split('\n'); + } else { + lines = source; + } + line = 0; + nextLine(); + from = 1; + }, + + range: function (begin, end) { + var c, value = ''; + from = character; + if (s.charAt(0) !== begin) { + errorAt("Expected '{a}' and instead saw '{b}'.", + line, character, begin, s.charAt(0)); + } + for (;;) { + s = s.slice(1); + character += 1; + c = s.charAt(0); + switch (c) { + case '': + errorAt("Missing '{a}'.", line, character, c); + break; + case end: + s = s.slice(1); + character += 1; + return it('(range)', value); + case xquote: + case '\\': + warningAt("Unexpected '{a}'.", line, character, c); + break; + } + value += c; + } + }, + +// token -- this is called by advance to get the next token. + + token: function () { + var b, c, captures, d, depth, high, i, l, low, q, t; + + function match(x) { + var r = x.exec(s), r1; + if (r) { + l = r[0].length; + r1 = r[1]; + c = r1.charAt(0); + s = s.substr(l); + from = character + l - r1.length; + character += l; + return r1; + } + } + + function string(x) { + var c, j, r = ''; + + if (jsonmode && x !== '"') { + warningAt("Strings must use doublequote.", + line, character); + } + + if (xquote === x || (xmode === 'scriptstring' && !xquote)) { + return it('(punctuator)', x); + } + + function esc(n) { + var i = parseInt(s.substr(j + 1, n), 16); + j += n; + if (i >= 32 && i <= 126 && + i !== 34 && i !== 92 && i !== 39) { + warningAt("Unnecessary escapement.", line, character); + } + character += n; + c = String.fromCharCode(i); + } + j = 0; + for (;;) { + while (j >= s.length) { + j = 0; + if (xmode !== 'html' || !nextLine()) { + errorAt("Unclosed string.", line, from); + } + } + c = s.charAt(j); + if (c === x) { + character += 1; + s = s.substr(j + 1); + return it('(string)', r, x); + } + if (c < ' ') { + if (c === '\n' || c === '\r') { + break; + } + warningAt("Control character in string: {a}.", + line, character + j, s.slice(0, j)); + } else if (c === xquote) { + warningAt("Bad HTML string", line, character + j); + } else if (c === '<') { + if (option.safe && xmode === 'html') { + warningAt("ADsafe string violation.", + line, character + j); + } else if (s.charAt(j + 1) === '/' && (xmode || option.safe)) { + warningAt("Expected '<\\/' and instead saw ' 0) { + character += 1; + s = s.slice(i); + break; + } else { + if (!nextLine()) { + return it('(end)', ''); + } + } + } + t = match(rx[xmode] || tx); + if (!t) { + t = ''; + c = ''; + while (s && s < '!') { + s = s.substr(1); + } + if (s) { + if (xmode === 'html') { + return it('(error)', s.charAt(0)); + } else { + errorAt("Unexpected '{a}'.", + line, character, s.substr(0, 1)); + } + } + } else { + + // identifier + + if (c.isAlpha() || c === '_' || c === '$') { + return it('(identifier)', t); + } + + // number + + if (c.isDigit()) { + if (xmode !== 'style' && !isFinite(Number(t))) { + warningAt("Bad number '{a}'.", + line, character, t); + } + if (xmode !== 'style' && + xmode !== 'styleproperty' && + s.substr(0, 1).isAlpha()) { + warningAt("Missing space after '{a}'.", + line, character, t); + } + if (c === '0') { + d = t.substr(1, 1); + if (d.isDigit()) { + if (token.id !== '.' && xmode !== 'styleproperty') { + warningAt("Don't use extra leading zeros '{a}'.", + line, character, t); + } + } else if (jsonmode && (d === 'x' || d === 'X')) { + warningAt("Avoid 0x-. '{a}'.", + line, character, t); + } + } + if (t.substr(t.length - 1) === '.') { + warningAt( +"A trailing decimal point can be confused with a dot '{a}'.", line, character, t); + } + return it('(number)', t); + } + switch (t) { + + // string + + case '"': + case "'": + return string(t); + + // // comment + + case '//': + if (src || (xmode && xmode !== 'script')) { + warningAt("Unexpected comment.", line, character); + } else if (xmode === 'script' && /<\s*\//i.test(s)) { + warningAt("Unexpected <\/ in comment.", line, character); + } else if ((option.safe || xmode === 'script') && ax.test(s)) { + warningAt("Dangerous comment.", line, character); + } + s = ''; + token.comment = true; + break; + + // /* comment + + case '/*': + if (src || (xmode && xmode !== 'script' && xmode !== 'style' && xmode !== 'styleproperty')) { + warningAt("Unexpected comment.", line, character); + } + if (option.safe && ax.test(s)) { + warningAt("ADsafe comment violation.", line, character); + } + for (;;) { + i = s.search(lx); + if (i >= 0) { + break; + } + if (!nextLine()) { + errorAt("Unclosed comment.", line, character); + } else { + if (option.safe && ax.test(s)) { + warningAt("ADsafe comment violation.", + line, character); + } + } + } + character += i + 2; + if (s.substr(i, 1) === '/') { + errorAt("Nested comment.", line, character); + } + s = s.substr(i + 2); + token.comment = true; + break; + + // /*members /*jslint /*global + + case '/*members': + case '/*member': + case '/*jslint': + case '/*global': + case '*/': + return { + value: t, + type: 'special', + line: line, + thru: character, + from: from + }; + + case '': + break; + // / + case '/': + if (token.id === '/=') { + errorAt( +"A regular expression literal can be confused with '/='.", line, from); + } + if (prereg) { + depth = 0; + captures = 0; + l = 0; + for (;;) { + b = true; + c = s.charAt(l); + l += 1; + switch (c) { + case '': + errorAt("Unclosed regular expression.", + line, from); + return; + case '/': + if (depth > 0) { + warningAt("Unescaped '{a}'.", + line, from + l, '/'); + } + c = s.substr(0, l - 1); + q = { + g: true, + i: true, + m: true + }; + while (q[s.charAt(l)] === true) { + q[s.charAt(l)] = false; + l += 1; + } + character += l; + s = s.substr(l); + q = s.charAt(0); + if (q === '/' || q === '*') { + errorAt("Confusing regular expression.", + line, from); + } + return it('(regexp)', c); + case '\\': + c = s.charAt(l); + if (c < ' ') { + warningAt( +"Unexpected control character in regular expression.", line, from + l); + } else if (c === '<') { + warningAt( +"Unexpected escaped character '{a}' in regular expression.", line, from + l, c); + } + l += 1; + break; + case '(': + depth += 1; + b = false; + if (s.charAt(l) === '?') { + l += 1; + switch (s.charAt(l)) { + case ':': + case '=': + case '!': + l += 1; + break; + default: + warningAt( +"Expected '{a}' and instead saw '{b}'.", line, from + l, ':', s.charAt(l)); + } + } else { + captures += 1; + } + break; + case '|': + b = false; + break; + case ')': + if (depth === 0) { + warningAt("Unescaped '{a}'.", + line, from + l, ')'); + } else { + depth -= 1; + } + break; + case ' ': + q = 1; + while (s.charAt(l) === ' ') { + l += 1; + q += 1; + } + if (q > 1) { + warningAt( +"Spaces are hard to count. Use {{a}}.", line, from + l, q); + } + break; + case '[': + c = s.charAt(l); + if (c === '^') { + l += 1; + if (option.regexp) { + warningAt("Insecure '{a}'.", + line, from + l, c); + } else if (s.charAt(l) === ']') { + errorAt("Unescaped '{a}'.", + line, from + l, '^'); + } + } + q = false; + if (c === ']') { + warningAt("Empty class.", line, + from + l - 1); + q = true; + } +klass: do { + c = s.charAt(l); + l += 1; + switch (c) { + case '[': + case '^': + warningAt("Unescaped '{a}'.", + line, from + l, c); + q = true; + break; + case '-': + if (q) { + q = false; + } else { + warningAt("Unescaped '{a}'.", + line, from + l, '-'); + q = true; + } + break; + case ']': + if (!q) { + warningAt("Unescaped '{a}'.", + line, from + l - 1, '-'); + } + break klass; + case '\\': + c = s.charAt(l); + if (c < ' ') { + warningAt( +"Unexpected control character in regular expression.", line, from + l); + } else if (c === '<') { + warningAt( +"Unexpected escaped character '{a}' in regular expression.", line, from + l, c); + } + l += 1; + q = true; + break; + case '/': + warningAt("Unescaped '{a}'.", + line, from + l - 1, '/'); + q = true; + break; + case '<': + if (xmode === 'script') { + c = s.charAt(l); + if (c === '!' || c === '/') { + warningAt( +"HTML confusion in regular expression '<{a}'.", line, from + l, c); + } + } + q = true; + break; + default: + q = true; + } + } while (c); + break; + case '.': + if (option.regexp) { + warningAt("Insecure '{a}'.", line, + from + l, c); + } + break; + case ']': + case '?': + case '{': + case '}': + case '+': + case '*': + warningAt("Unescaped '{a}'.", line, + from + l, c); + break; + case '<': + if (xmode === 'script') { + c = s.charAt(l); + if (c === '!' || c === '/') { + warningAt( +"HTML confusion in regular expression '<{a}'.", line, from + l, c); + } + } + break; + } + if (b) { + switch (s.charAt(l)) { + case '?': + case '+': + case '*': + l += 1; + if (s.charAt(l) === '?') { + l += 1; + } + break; + case '{': + l += 1; + c = s.charAt(l); + if (c < '0' || c > '9') { + warningAt( +"Expected a number and instead saw '{a}'.", line, from + l, c); + } + l += 1; + low = +c; + for (;;) { + c = s.charAt(l); + if (c < '0' || c > '9') { + break; + } + l += 1; + low = +c + (low * 10); + } + high = low; + if (c === ',') { + l += 1; + high = Infinity; + c = s.charAt(l); + if (c >= '0' && c <= '9') { + l += 1; + high = +c; + for (;;) { + c = s.charAt(l); + if (c < '0' || c > '9') { + break; + } + l += 1; + high = +c + (high * 10); + } + } + } + if (s.charAt(l) !== '}') { + warningAt( +"Expected '{a}' and instead saw '{b}'.", line, from + l, '}', c); + } else { + l += 1; + } + if (s.charAt(l) === '?') { + l += 1; + } + if (low > high) { + warningAt( +"'{a}' should not be greater than '{b}'.", line, from + l, low, high); + } + break; + } + } + } + c = s.substr(0, l - 1); + character += l; + s = s.substr(l); + return it('(regexp)', c); + } + return it('(punctuator)', t); + + // punctuator + + case '.", line, character); + } + character += 3; + s = s.slice(i + 3); + break; + case '#': + if (xmode === 'html' || xmode === 'styleproperty') { + for (;;) { + c = s.charAt(0); + if ((c < '0' || c > '9') && + (c < 'a' || c > 'f') && + (c < 'A' || c > 'F')) { + break; + } + character += 1; + s = s.substr(1); + t += c; + } + if (t.length !== 4 && t.length !== 7) { + warningAt("Bad hex color '{a}'.", line, + from + l, t); + } + return it('(color)', t); + } + return it('(punctuator)', t); + default: + if (xmode === 'outer' && c === '&') { + character += 1; + s = s.substr(1); + for (;;) { + c = s.charAt(0); + character += 1; + s = s.substr(1); + if (c === ';') { + break; + } + if (!((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'z') || + c === '#')) { + errorAt("Bad entity", line, from + l, + character); + } + } + break; + } + return it('(punctuator)', t); + } + } + } + } + }; + }()); + + + function addlabel(t, type) { + + if (option.safe && funct['(global)'] && + typeof predefined[t] !== 'boolean') { + warning('ADsafe global: ' + t + '.', token); + } else if (t === 'hasOwnProperty') { + warning("'hasOwnProperty' is a really bad name."); + } + +// Define t in the current function in the current scope. + + if (is_own(funct, t) && !funct['(global)']) { + warning(funct[t] === true ? + "'{a}' was used before it was defined." : + "'{a}' is already defined.", + nexttoken, t); + } + funct[t] = type; + if (funct['(global)']) { + global[t] = funct; + if (is_own(implied, t)) { + warning("'{a}' was used before it was defined.", nexttoken, t); + delete implied[t]; + } + } else { + scope[t] = funct; + } + } + + + function doOption() { + var b, obj, filter, o = nexttoken.value, t, v; + switch (o) { + case '*/': + error("Unbegun comment."); + break; + case '/*members': + case '/*member': + o = '/*members'; + if (!membersOnly) { + membersOnly = {}; + } + obj = membersOnly; + break; + case '/*jslint': + if (option.safe) { + warning("ADsafe restriction."); + } + obj = option; + filter = boolOptions; + break; + case '/*global': + if (option.safe) { + warning("ADsafe restriction."); + } + obj = predefined; + break; + default: + error("What?"); + } + t = lex.token(); +loop: for (;;) { + for (;;) { + if (t.type === 'special' && t.value === '*/') { + break loop; + } + if (t.id !== ',') { + break; + } + t = lex.token(); + } + if (t.type !== '(string)' && t.type !== '(identifier)' && + o !== '/*members') { + error("Bad option.", t); + } + v = lex.token(); + if (v.id === ':') { + v = lex.token(); + if (obj === membersOnly) { + error("Expected '{a}' and instead saw '{b}'.", + t, '*/', ':'); + } + if (t.value === 'indent' && o === '/*jslint') { + b = +v.value; + if (typeof b !== 'number' || !isFinite(b) || b <= 0 || + Math.floor(b) !== b) { + error("Expected a small integer and instead saw '{a}'.", + v, v.value); + } + obj.white = true; + obj.indent = b; + } else if (t.value === 'maxerr' && o === '/*jslint') { + b = +v.value; + if (typeof b !== 'number' || !isFinite(b) || b <= 0 || + Math.floor(b) !== b) { + error("Expected a small integer and instead saw '{a}'.", + v, v.value); + } + obj.maxerr = b; + } else if (t.value === 'maxlen' && o === '/*jslint') { + b = +v.value; + if (typeof b !== 'number' || !isFinite(b) || b <= 0 || + Math.floor(b) !== b) { + error("Expected a small integer and instead saw '{a}'.", + v, v.value); + } + obj.maxlen = b; + } else if (v.value === 'true') { + obj[t.value] = true; + } else if (v.value === 'false') { + obj[t.value] = false; + } else { + error("Bad option value.", v); + } + t = lex.token(); + } else { + if (o === '/*jslint') { + error("Missing option value.", t); + } + obj[t.value] = false; + t = v; + } + } + if (filter) { + assume(); + } + } + + +// We need a peek function. If it has an argument, it peeks that much farther +// ahead. It is used to distinguish +// for ( var i in ... +// from +// for ( var i = ... + + function peek(p) { + var i = p || 0, j = 0, t; + + while (j <= i) { + t = lookahead[j]; + if (!t) { + t = lookahead[j] = lex.token(); + } + j += 1; + } + return t; + } + + + +// Produce the next token. It looks for programming errors. + + function advance(id, t) { + switch (token.id) { + case '(number)': + if (nexttoken.id === '.') { + warning( +"A dot following a number can be confused with a decimal point.", token); + } + break; + case '-': + if (nexttoken.id === '-' || nexttoken.id === '--') { + warning("Confusing minusses."); + } + break; + case '+': + if (nexttoken.id === '+' || nexttoken.id === '++') { + warning("Confusing plusses."); + } + break; + } + if (token.type === '(string)' || token.identifier) { + anonname = token.value; + } + + if (id && nexttoken.id !== id) { + if (t) { + if (nexttoken.id === '(end)') { + warning("Unmatched '{a}'.", t, t.id); + } else { + warning( +"Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.", + nexttoken, id, t.id, t.line, nexttoken.value); + } + } else if (nexttoken.type !== '(identifier)' || + nexttoken.value !== id) { + warning("Expected '{a}' and instead saw '{b}'.", + nexttoken, id, nexttoken.value); + } + } + prevtoken = token; + token = nexttoken; + for (;;) { + nexttoken = lookahead.shift() || lex.token(); + if (nexttoken.type !== 'special') { + break; + } + doOption(); + } + } + +// Functions for conformance of style. + + function one_space_only(left, right) { + left = left || token; + right = right || nexttoken; + if (right.id !== '(end)' && (left.line !== right.line || + (option.white && left.thru + 1 !== right.from))) { + warning("Expected exactly one space between '{a}' and '{b}'.", + right, left.value, right.value); + } + } + + function one_space(left, right) { + left = left || token; + right = right || nexttoken; + if (right.id !== '(end)' && option.white && + (token.line !== right.line || + token.thru + 1 !== right.from)) { + warning("Expected exactly one space between '{a}' and '{b}'.", + right, token.value, right.value); + } + } + + function no_space(left, right) { + left = left || token; + right = right || nexttoken; + if ((option.white || xmode === 'styleproperty' || xmode === 'style') && + left.thru !== right.from && left.line === right.line) { + warning("Unexpected space between '{a}' and '{b}'.", right, + left.value, right.value); + } + } + + function no_space_only(left, right) { + left = left || token; + right = right || nexttoken; + if (right.id !== '(end)' && (left.line !== right.line || + (option.white && left.thru !== right.from))) { + warning("Unexpected space between '{a}' and '{b}'.", + right, left.value, right.value); + } + } + + function spaces(left, right) { + if (option.white) { + left = left || token; + right = right || nexttoken; + if (left.thru === right.from && left.line === right.line) { + warning("Missing space between '{a}' and '{b}'.", + right, left.value, right.value); + } + } + } + + function comma() { + no_space_only(); + advance(','); + spaces(); + } + + + function semicolon() { + no_space_only(); + advance(';'); + switch (nexttoken.id) { + case ';': + case '"': + case '\'': + case ')': + break; + default: + spaces(); + } + } + + function use_strict() { + if (nexttoken.value === 'use strict') { + if (strict_mode) { + warning("Unnecessary \"use strict\"."); + } + advance(); + semicolon(); + strict_mode = true; + option.newcap = true; + option.undef = true; + return true; + } else { + return false; + } + } + + +// This is the heart of JSLINT, the Pratt parser. In addition to parsing, it +// is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is +// like .nud except that it is only used on the first token of a statement. +// Having .fud makes it much easier to define statement-oriented languages like +// JavaScript. I retained Pratt's nomenclature. + +// .nud Null denotation +// .fud First null denotation +// .led Left denotation +// lbp Left binding power +// rbp Right binding power + +// They are elements of the parsing method called Top Down Operator Precedence. + + function expression(rbp, initial) { + +// rbp is the right binding power. +// initial indicates that this is the first expression of a statement. + + var left; + if (nexttoken.id === '(end)') { + error("Unexpected early end of program.", token); + } + advance(); + if (option.safe && typeof predefined[token.value] === 'boolean' && + (nexttoken.id !== '(' && nexttoken.id !== '.')) { + warning('ADsafe violation.', token); + } + if (initial) { + anonname = 'anonymous'; + funct['(verb)'] = token.value; + } + if (initial === true && token.fud) { + left = token.fud(); + } else { + if (token.nud) { + left = token.nud(); + } else { + if (nexttoken.type === '(number)' && token.id === '.') { + warning( +"A leading decimal point can be confused with a dot: '.{a}'.", + token, nexttoken.value); + advance(); + return token; + } else { + error("Expected an identifier and instead saw '{a}'.", + token, token.id); + } + } + while (rbp < nexttoken.lbp) { + advance(); + if (token.led) { + left = token.led(left); + } else { + error("Expected an operator and instead saw '{a}'.", + token, token.id); + } + } + } + return left; + } + + +// Functional constructors for making the symbols that will be inherited by +// tokens. + + function symbol(s, p) { + var x = syntax[s]; + if (!x || typeof x !== 'object') { + syntax[s] = x = { + id: s, + lbp: p, + value: s + }; + } + return x; + } + + + function delim(s) { + return symbol(s, 0); + } + + + function ultimate(s) { + var x = symbol(s, 0); + x.from = 0; + x.thru = 0; + s.value = s; + return x; + } + + + function stmt(s, f) { + var x = delim(s); + x.identifier = x.reserved = true; + x.fud = f; + return x; + } + + function disruptstmt(s, f) { + var x = stmt(s, f); + x.disrupt = true; + } + + + function reserveName(x) { + var c = x.id.charAt(0); + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + x.identifier = x.reserved = true; + } + return x; + } + + + function prefix(s, f) { + var x = symbol(s, 150); + reserveName(x); + x.nud = (typeof f === 'function') ? f : function () { + if (s === 'typeof') { + one_space(); + } else { + no_space_only(); + } + this.first = expression(150); + this.arity = 'prefix'; + if (this.id === '++' || this.id === '--') { + if (option.plusplus) { + warning("Unexpected use of '{a}'.", this, this.id); + } else if ((!this.first.identifier || this.first.reserved) && + this.first.id !== '.' && this.first.id !== '[') { + warning("Bad operand.", this); + } + } + return this; + }; + return x; + } + + + function type(s, f) { + var x = delim(s); + x.type = s; + x.nud = f; + return x; + } + + + function reserve(s, f) { + var x = type(s, f); + x.identifier = x.reserved = true; + return x; + } + + + function reservevar(s, v) { + return reserve(s, function () { + if (typeof v === 'function') { + v(this); + } + return this; + }); + } + + + function infix(s, p, f, w) { + var x = symbol(s, p); + reserveName(x); + x.led = function (left) { + this.arity = 'infix'; + if (!w) { + spaces(prevtoken, token); + spaces(); + } + if (typeof f === 'function') { + return f(left, this); + } else { + this.first = left; + this.second = expression(p); + return this; + } + }; + return x; + } + + + function relation(s, eqeq) { + var x = infix(s, 100, function (left, that) { + var right = expression(100); + if (eqeq) { + warning("Expected '{a}' and instead saw '{b}'.", + that, eqeq, that.id); + } else if (left.id === 'NaN' || right.id === 'NaN') { + warning("Use the isNaN function to compare with NaN.", that); + } + if (left.id === '!') { + warning("Confusing use of '{a}'.", left, '!'); + } + if (right.id === '!') { + warning("Confusing use of '{a}'.", left, '!'); + } + that.first = left; + that.second = right; + return that; + }); + return x; + } + + + function assignop(s, bit) { + var x = infix(s, 20, function (left, that) { + var l; + if (option.bitwise && bit) { + warning("Unexpected use of '{a}'.", that, that.id); + } + that.first = left; + if (predefined[left.value] === false && + scope[left.value]['(global)'] === true) { + warning("Read only.", left); + } else if (left['function']) { + warning("'{a}' is a function.", left, left.value); + } + if (option.safe) { + l = left; + do { + if (typeof predefined[l.value] === 'boolean') { + warning('ADsafe violation.', l); + } + l = l.first; + } while (l); + } + if (left) { + if (left.id === '.' || left.id === '[') { + if (!left.first || left.first.value === 'arguments') { + warning('Bad assignment.', that); + } + that.second = expression(19); + return that; + } else if (left.identifier && !left.reserved) { + if (funct[left.value] === 'exception') { + warning("Do not assign to the exception parameter.", left); + } + that.second = expression(19); + return that; + } + if (left === syntax['function']) { + warning( +"Expected an identifier in an assignment and instead saw a function invocation.", + token); + } + } + error("Bad assignment.", that); + }); + x.assign = true; + return x; + } + + + function bitwise(s, p) { + return infix(s, p, function (left, that) { + if (option.bitwise) { + warning("Unexpected use of '{a}'.", that, that.id); + } + that.first = left; + that.second = expression(p); + return that; + }); + } + + + function suffix(s, f) { + var x = symbol(s, 150); + x.led = function (left) { + no_space_only(prevtoken, token); + if (option.plusplus) { + warning("Unexpected use of '{a}'.", this, this.id); + } else if ((!left.identifier || left.reserved) && + left.id !== '.' && left.id !== '[') { + warning("Bad operand.", this); + } + this.first = left; + this.arity = 'suffix'; + return this; + }; + return x; + } + + + function optionalidentifier() { + if (nexttoken.identifier) { + advance(); + if (option.safe && banned[token.value]) { + warning("ADsafe violation: '{a}'.", token, token.value); + } else if (token.reserved && !option.es5) { + warning("Expected an identifier and instead saw '{a}' (a reserved word).", + token, token.id); + } + return token.value; + } + } + + + function identifier() { + var i = optionalidentifier(); + if (i) { + return i; + } + if (token.id === 'function' && nexttoken.id === '(') { + warning("Missing name in function statement."); + } else { + error("Expected an identifier and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + } + + + function statement(noindent) { + +// Usually a statement starts a line. Exceptions include the var statement in the +// initialization part of a for statement, and an if after an else. + + var r, s = scope, t = nexttoken; + +// We don't like the empty statement. + + if (t.id === ';') { + warning("Unnecessary semicolon.", t); + advance(';'); + return; + } + +// Is this a labelled statement? + + if (t.identifier && !t.reserved && peek().id === ':') { + advance(); + advance(':'); + scope = Object.create(s); + addlabel(t.value, 'label'); + if (labelled[nexttoken.id] !== true) { + warning("Label '{a}' on '{b}' statement.", + nexttoken, t.value, nexttoken.value); + } + if (jx.test(t.value + ':')) { + warning("Label '{a}' looks like a javascript url.", + t, t.value); + } + nexttoken.label = t.value; + t = nexttoken; + } + +// Parse the statement. + + r = expression(0, true); + +// Look for the final semicolon. + + if (r.arity === 'statement') { + if (r.id !== 'switch' && (!r.block || r.id === 'do')) { + semicolon(); + } else { + spaces(); + } + } else { + if (r.id === '(' && r.first.id === 'new') { + warning("Do not use 'new' for side effects."); + } else if (!r.assign && r.id !== 'delete' && r.id !== '++' && + r.id !== '--' && r.id !== '(') { + warning( +"Expected an assignment or function call and instead saw an expression.", + token); + } + if (nexttoken.id !== ';') { + warningAt("Missing semicolon.", token.line, + token.from + token.value.length); + } else { + semicolon(); + } + } + scope = s; + return r; + } + + + function statements(begin) { + var a = [], d, f, p, s; + if (option.adsafe) { + switch (begin) { + case 'script': + +// JSLint is also the static analizer for ADsafe. See www.ADsafe.org. + + if (!adsafe_may) { + if (nexttoken.value !== 'ADSAFE' || + peek(0).id !== '.' || + (peek(1).value !== 'id' && + peek(1).value !== 'go')) { + error('ADsafe violation: Missing ADSAFE.id or ADSAFE.go.', + nexttoken); + } + } + if (nexttoken.value === 'ADSAFE' && + peek(0).id === '.' && + peek(1).value === 'id') { + if (adsafe_may) { + error('ADsafe violation.', nexttoken); + } + advance('ADSAFE'); + advance('.'); + advance('id'); + advance('('); + if (nexttoken.value !== adsafe_id) { + error('ADsafe violation: id does not match.', nexttoken); + } + advance('(string)'); + advance(')'); + semicolon(); + adsafe_may = true; + } + break; + case 'lib': + if (nexttoken.value === 'ADSAFE') { + advance('ADSAFE'); + advance('.'); + advance('lib'); + advance('('); + advance('(string)'); + comma(); + f = expression(0); + if (f.id !== 'function') { + error('The second argument to lib must be a function.', f); + } + p = f.funct['(params)']; + p = p && p.join(', '); + if (p && p !== 'lib') { + error("Expected '{a}' and instead saw '{b}'.", + f, '(lib)', '(' + p + ')'); + } + advance(')'); + semicolon(); + return a; + } else { + error("ADsafe lib violation."); + } + break; + } + } + +// A disrupt statement may not be followed by any other statement. +// If the last statement is disrupt, then the sequence is disrupt. + + while (postscript[nexttoken.id] !== true) { + if (nexttoken.id === ';') { + warning("Unnecessary semicolon."); + advance(';'); + } else { + if (d) { + warning("Unreachable '{a}' after '{b}'.", nexttoken, + nexttoken.value, d.value); + d = null; + } + s = statement(); + a.push(s); + if (s.disrupt) { + d = s; + a.disrupt = true; + } + } + } + return a; + } + + + function block(ordinary) { + +// A block is a sequence of statements wrapped in braces. +// ordinary is false for function bodies and try blocks. +// ordinary is true for if statements, while, etc. + + var a, + b = inblock, + m = strict_mode, + s = scope, + t; + inblock = ordinary; + scope = Object.create(scope); + spaces(); + t = nexttoken; + if (nexttoken.id === '{') { + advance('{'); + if (!ordinary && !use_strict() && !m && option.strict && + funct['(context)']['(global)']) { + warning("Missing \"use strict\" statement."); + } + a = statements(); + strict_mode = m; + advance('}', t); + } else if (!ordinary) { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, '{', nexttoken.value); + } else { + warning("Expected '{a}' and instead saw '{b}'.", + nexttoken, '{', nexttoken.value); + a = [statement()]; + if (a[0].disrupt) { + a.disrupt = true; + } + } + funct['(verb)'] = null; + scope = s; + inblock = b; + if (ordinary && a.length === 0) { + warning("Empty block."); + } + return a; + } + + + function countMember(m) { + if (membersOnly && typeof membersOnly[m] !== 'boolean') { + warning("Unexpected /*member '{a}'.", token, m); + } + if (typeof member[m] === 'number') { + member[m] += 1; + } else { + member[m] = 1; + } + } + + + function note_implied(token) { + var name = token.value, line = token.line, a = implied[name]; + if (typeof a === 'function') { + a = false; + } + if (!a) { + a = [line]; + implied[name] = a; + } else if (a[a.length - 1] !== line) { + a.push(line); + } + } + + +// Build the syntax table by declaring the syntactic elements of the language. + + type('(number)', function () { + this.arity = 'number'; + return this; + }); + type('(string)', function () { + this.arity = 'string'; + return this; + }); + + syntax['(identifier)'] = { + type: '(identifier)', + lbp: 0, + identifier: true, + nud: function () { + var v = this.value, + s = scope[v], + f; + if (typeof s === 'function') { + +// Protection against accidental inheritance. + + s = undefined; + } else if (typeof s === 'boolean') { + f = funct; + funct = functions[0]; + addlabel(v, 'var'); + s = funct; + funct = f; + } + +// The name is in scope and defined in the current function. + + if (funct === s) { + +// Change 'unused' to 'var', and reject labels. + + switch (funct[v]) { + case 'unused': + funct[v] = 'var'; + break; + case 'unction': + funct[v] = 'function'; + this['function'] = true; + break; + case 'function': + this['function'] = true; + break; + case 'label': + warning("'{a}' is a statement label.", token, v); + break; + } + +// The name is not defined in the function. If we are in the global scope, +// then we have an undefined variable. + + } else if (funct['(global)']) { + if (option.undef && typeof predefined[v] !== 'boolean') { + warning("'{a}' is not defined.", token, v); + } + note_implied(token); + +// If the name is already defined in the current +// function, but not as outer, then there is a scope error. + + } else { + switch (funct[v]) { + case 'closure': + case 'function': + case 'var': + case 'unused': + warning("'{a}' used out of scope.", token, v); + break; + case 'label': + warning("'{a}' is a statement label.", token, v); + break; + case 'outer': + case 'global': + break; + default: + +// If the name is defined in an outer function, make an outer entry, and if +// it was unused, make it var. + + if (s === true) { + funct[v] = true; + } else if (s === null) { + warning("'{a}' is not allowed.", token, v); + note_implied(token); + } else if (typeof s !== 'object') { + if (option.undef) { + warning("'{a}' is not defined.", token, v); + } else { + funct[v] = true; + } + note_implied(token); + } else { + switch (s[v]) { + case 'function': + case 'unction': + this['function'] = true; + s[v] = 'closure'; + funct[v] = s['(global)'] ? 'global' : 'outer'; + break; + case 'var': + case 'unused': + s[v] = 'closure'; + funct[v] = s['(global)'] ? 'global' : 'outer'; + break; + case 'closure': + case 'parameter': + funct[v] = s['(global)'] ? 'global' : 'outer'; + break; + case 'label': + warning("'{a}' is a statement label.", token, v); + break; + } + } + } + } + return this; + }, + led: function () { + error("Expected an operator and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + }; + + type('(regexp)', function () { + return this; + }); + + +// ECMAScript parser + + ultimate('(begin)'); + ultimate('(end)'); + ultimate('(error)'); + delim(''); + delim('}'); + delim(')'); + delim(']'); + delim('"'); + delim("'"); + delim(';'); + delim(':'); + delim(','); + delim('#'); + delim('@'); + reserve('else'); + reserve('case'); + reserve('catch'); + reserve('default'); + reserve('finally'); + reservevar('arguments', function (x) { + if (strict_mode && funct['(global)']) { + warning("Strict violation.", x); + } else if (option.safe) { + warning("ADsafe violation.", x); + } + }); + reservevar('eval', function (x) { + if (option.safe) { + warning("ADsafe violation.", x); + } + }); + reservevar('false'); + reservevar('Infinity'); + reservevar('NaN'); + reservevar('null'); + reservevar('this', function (x) { + if (strict_mode && ((funct['(statement)'] && //// correct this test. + funct['(name)'].charAt(0) > 'Z') || funct['(global)'])) { + warning("Strict violation.", x); + } else if (option.safe) { + warning("ADsafe violation.", x); + } + }); + reservevar('true'); + reservevar('undefined'); + assignop('='); + assignop('+='); + assignop('-='); + assignop('*='); + assignop('/=').nud = function () { + error("A regular expression literal can be confused with '/='."); + }; + assignop('%='); + assignop('&=', true); + assignop('|=', true); + assignop('^=', true); + assignop('<<=', true); + assignop('>>=', true); + assignop('>>>=', true); + infix('?', 30, function (left, that) { + that.first = left; + that.second = expression(10); + spaces(); + advance(':'); + spaces(); + that.third = expression(10); + return that; + }); + + infix('||', 40); + infix('&&', 50); + bitwise('|', 70); + bitwise('^', 80); + bitwise('&', 90); + relation('==', '==='); + relation('==='); + relation('!=', '!=='); + relation('!=='); + relation('<'); + relation('>'); + relation('<='); + relation('>='); + bitwise('<<', 120); + bitwise('>>', 120); + bitwise('>>>', 120); + infix('in', 120); + infix('instanceof', 120); + infix('+', 130, function (left, that) { + var right = expression(130); + if (left && right && left.id === '(string)' && right.id === '(string)') { + left.value += right.value; + left.thru = right.thru; + if (jx.test(left.value)) { + warning("JavaScript URL.", left); + } + return left; + } + that.first = left; + that.second = right; + return that; + }); + prefix('+', 'num'); + prefix('+++', function () { + warning("Confusing pluses."); + this.first = expression(150); + this.arity = 'prefix'; + return this; + }); + infix('+++', 130, function (left) { + warning("Confusing pluses."); + this.first = left; + this.second = expression(130); + return this; + }); + infix('-', 130); + prefix('-'); + prefix('---', function () { + warning("Confusing minuses."); + this.first = expression(150); + this.arity = 'prefix'; + return this; + }); + infix('---', 130, function (left) { + warning("Confusing minuses."); + this.first = left; + this.second = expression(130); + return this; + }); + infix('*', 140); + infix('/', 140); + infix('%', 140); + + suffix('++'); + prefix('++'); + + suffix('--'); + prefix('--'); + prefix('delete', function () { + one_space(); + var p = expression(0); + if (!p || (p.id !== '.' && p.id !== '[')) { + warning("Only properties should be deleted."); + } + this.first = p; + return this; + }); + + + prefix('~', function () { + no_space_only(); + if (option.bitwise) { + warning("Unexpected '{a}'.", this, '~'); + } + expression(150); + return this; + }); + prefix('!', function () { + no_space_only(); + this.first = expression(150); + this.arity = 'prefix'; + if (bang[this.first.id] === true) { + warning("Confusing use of '{a}'.", this, '!'); + } + return this; + }); + prefix('typeof'); + prefix('new', function () { + one_space(); + var c = expression(160), i; + if (c.id !== 'function') { + if (c.identifier) { + switch (c.value) { + case 'Object': + warning("Use the object literal notation {}.", token); + break; + case 'Array': + if (nexttoken.id !== '(') { + warning("Use the array literal notation [].", token); + } else { + advance('('); + if (nexttoken.id === ')') { + warning("Use the array literal notation [].", token); + } + advance(')'); + } + this.first = c; + return this; + case 'Number': + case 'String': + case 'Boolean': + case 'Math': + case 'JSON': + warning("Do not use {a} as a constructor.", token, c.value); + break; + case 'Function': + if (!option.evil) { + warning("The Function constructor is eval."); + } + break; + case 'Date': + case 'RegExp': + break; + default: + if (c.id !== 'function') { + i = c.value.substr(0, 1); + if (option.newcap && (i < 'A' || i > 'Z')) { + warning( + "A constructor name should start with an uppercase letter.", + token); + } + } + } + } else { + if (c.id !== '.' && c.id !== '[' && c.id !== '(') { + warning("Bad constructor.", token); + } + } + } else { + warning("Weird construction. Delete 'new'.", this); + } + if (nexttoken.id !== '(') { + warning("Missing '()' invoking a constructor."); + } + this.first = c; + return this; + }); + + infix('(', 160, function (left, that) { + no_space_only(prevtoken, token); + if (!left.immed && left.id === 'function') { + warning("Wrap an immediate function invocation in parentheses " + + "to assist the reader in understanding that the expression " + + "is the result of a function, and not the function itself."); + } + var p = []; + if (left) { + if (left.type === '(identifier)') { + if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) { + if (left.value !== 'Number' && left.value !== 'String' && + left.value !== 'Boolean' && + left.value !== 'Date') { + if (left.value === 'Math') { + warning("Math is not a function.", left); + } else if (option.newcap) { + warning( +"Missing 'new' prefix when invoking a constructor.", left); + } + } + } + } else if (left.id === '.') { + if (option.safe && left.first.value === 'Math' && + left.second === 'random') { + warning("ADsafe violation.", left); + } + } + } + if (nexttoken.id !== ')') { + no_space(); + for (;;) { + p.push(expression(10)); + if (nexttoken.id !== ',') { + break; + } + comma(); + } + } + no_space(); + advance(')'); + if (typeof left === 'object') { + if (left.value === 'parseInt' && p.length === 1) { + warning("Missing radix parameter.", left); + } + if (!option.evil) { + if (left.value === 'eval' || left.value === 'Function' || + left.value === 'execScript') { + warning("eval is evil.", left); + } else if (p[0] && p[0].id === '(string)' && + (left.value === 'setTimeout' || + left.value === 'setInterval')) { + warning( + "Implied eval is evil. Pass a function instead of a string.", left); + } + } + if (!left.identifier && left.id !== '.' && left.id !== '[' && + left.id !== '(' && left.id !== '&&' && left.id !== '||' && + left.id !== '?') { + warning("Bad invocation.", left); + } + } + that.first = left; + that.second = p; + return that; + }, true); + + prefix('(', function () { + no_space(); + if (nexttoken.id === 'function') { + nexttoken.immed = true; + } + var v = expression(0); + no_space(); + advance(')', this); + if (v.id === 'function') { + if (nexttoken.id === '(') { + warning( +"Move the invocation into the parens that contain the function.", nexttoken); + } else { + warning( +"Do not wrap function literals in parens unless they are to be immediately invoked.", + this); + } + } + return v; + }); + + infix('.', 170, function (left, that) { + no_space(prevtoken, token); + no_space(); + var m = identifier(); + if (typeof m === 'string') { + countMember(m); + } + that.first = left; + that.second = m; + if (left && left.value === 'arguments' && + (m === 'callee' || m === 'caller')) { + warning("Avoid arguments.{a}.", left, m); + } else if (!option.evil && left && left.value === 'document' && + (m === 'write' || m === 'writeln')) { + warning("document.write can be a form of eval.", left); + } else if (option.adsafe) { + if (left && left.value === 'ADSAFE') { + if (m === 'id' || m === 'lib') { + warning("ADsafe violation.", that); + } else if (m === 'go') { + if (xmode !== 'script') { + warning("ADsafe violation.", that); + } else if (adsafe_went || nexttoken.id !== '(' || + peek(0).id !== '(string)' || + peek(0).value !== adsafe_id || + peek(1).id !== ',') { + error("ADsafe violation: go.", that); + } + adsafe_went = true; + adsafe_may = false; + } + } + } + if (!option.evil && (m === 'eval' || m === 'execScript')) { + warning('eval is evil.'); + } else if (option.safe) { + for (;;) { + if (banned[m] === true) { + warning("ADsafe restricted word '{a}'.", token, m); + } + if (typeof predefined[left.value] !== 'boolean' || + nexttoken.id === '(') { + break; + } + if (standard_member[m] === true) { + if (nexttoken.id === '.') { + warning("ADsafe violation.", that); + } + break; + } + if (nexttoken.id !== '.') { + warning("ADsafe violation.", that); + break; + } + advance('.'); + token.first = that; + token.second = m; + that = token; + m = identifier(); + if (typeof m === 'string') { + countMember(m); + } + } + } + return that; + }, true); + + infix('[', 170, function (left, that) { + no_space_only(prevtoken, token); + no_space(); + var e = expression(0), s; + if (e && e.type === '(string)') { + if (option.safe && banned[e.value] === true) { + warning("ADsafe restricted word '{a}'.", that, e.value); + } else if (!option.evil && + (e.value === 'eval' || e.value === 'execScript')) { + warning("eval is evil.", that); + } else if (option.safe && + (e.value.charAt(0) === '_' || e.value.charAt(0) === '-')) { + warning("ADsafe restricted subscript '{a}'.", that, e.value); + } + countMember(e.value); + if (!option.sub && ix.test(e.value)) { + s = syntax[e.value]; + if (!s || !s.reserved) { + warning("['{a}'] is better written in dot notation.", + e, e.value); + } + } + } else if (!e || e.type !== '(number)' || e.value < 0) { + if (option.safe) { + warning('ADsafe subscripting.'); + } + } + advance(']', that); + no_space(prevtoken, token); + that.first = left; + that.second = e; + return that; + }, true); + + prefix('[', function () { + this.first = []; + while (nexttoken.id !== '(end)') { + while (nexttoken.id === ',') { + warning("Extra comma."); + advance(','); + } + if (nexttoken.id === ']') { + break; + } + this.first.push(expression(10)); + if (nexttoken.id === ',') { + comma(); + if (nexttoken.id === ']' && !option.es5) { + warning("Extra comma.", token); + break; + } + } else { + break; + } + } + advance(']', this); + return this; + }, 170); + + + function property_name() { + var id = optionalidentifier(true); + if (!id) { + if (nexttoken.id === '(string)') { + id = nexttoken.value; + if (option.adsafe && + (id.charAt(0) === '_' || + id.charAt(id.length - 1) === '_')) { + warning("Unexpected {a} in '{b}'.", token, + "dangling '_'", id); + } + advance(); + } else if (nexttoken.id === '(number)') { + id = nexttoken.value.toString(); + advance(); + } + } + return id; + } + + + function functionparams() { + var i, t = nexttoken, p = []; + advance('('); + no_space(); + if (nexttoken.id === ')') { + no_space(); + advance(')'); + return; + } + for (;;) { + i = identifier(); + p.push(i); + addlabel(i, 'parameter'); + if (nexttoken.id === ',') { + comma(); + } else { + no_space(); + advance(')', t); + return p; + } + } + } + + + function doFunction(func, name) { + var s = scope; + scope = Object.create(s); + funct = { + '(name)' : name || '"' + anonname + '"', + '(line)' : nexttoken.line, + '(context)' : funct, + '(breakage)' : 0, + '(loopage)' : 0, + '(scope)' : scope, + '(token)' : func + }; + token.funct = funct; + functions.push(funct); + if (name) { + addlabel(name, 'function'); + } + func.name = name || ''; + func.first = funct['(params)'] = functionparams(); + func.block = block(false); + + scope = s; + funct['(last)'] = token.line; + funct = funct['(context)']; + return func; + } + + + prefix('{', function () { + var get, i, j, name, p, set, seen = {}, t; + this.arity = 'prefix'; + this.first = []; + while (nexttoken.id !== '}') { + +// JSLint recognizes the ES5 extension for get/set in object literals, +// but requires that they be used in pairs. + + if (nexttoken.value === 'get' && peek().id !== ':') { + if (!option.es5) { + warning("get/set are ES5 features."); + } + get = nexttoken; + one_space_only(); + advance('get'); + name = nexttoken; + i = property_name(); + if (!i) { + error("Missing property name."); + } + doFunction(get, ''); + if (funct['(loopage)']) { + warning("Don't make functions within a loop.", t); + } + p = get.first; + if (p) { + warning("Unexpected parameter '{a}' in get {b} function.", t, p[0], i); + } + comma(); + set = nexttoken; + spaces(); + advance('set'); + one_space_only(); + j = property_name(); + if (i !== j) { + error("Expected '{a}' and instead saw '{b}'.", token, i, j); + } + doFunction(set, ''); + p = set.first; + if (!p || p.length !== 1 || p[0] !== 'value') { + warning("Expected (value) in set {a} function.", t, i); + } + name.first = [get, set]; + } else { + name = nexttoken; + i = property_name(); + if (typeof i !== 'string') { + error("Missing property name."); + } + advance(':'); + spaces(); + name.first = expression(10); + } + this.first.push(name); + if (seen[i] === true) { + warning("Duplicate member '{a}'.", nexttoken, i); + } + seen[i] = true; + countMember(i); + if (nexttoken.id !== ',') { + break; + } + for (;;) { + comma(); + if (nexttoken.id !== ',') { + break; + } + warning("Extra comma."); + } + if (nexttoken.id === '}' && !option.es5) { + warning("Extra comma.", token); + } + } + advance('}', this); + return this; + }); + + stmt('{', function () { + warning("Expected to see a statement and instead saw a block."); + this.arity = 'statement'; + this.block = statements(); + this.disrupt = this.block.disrupt; + advance('}'); + return this; + }); + + + stmt('var', function () { + +// JavaScript does not have block scope. It only has function scope. So, +// declaring a variable in a block can have unexpected consequences. + +// var.first will contain an array, the array containing name tokens +// and assignment tokens. + + var assign, id, name; + + if (funct['(onevar)'] && option.onevar) { + warning("Too many var statements."); + } else if (!funct['(global)']) { + funct['(onevar)'] = true; + } + this.arity = 'statement'; + this.first = []; + for (;;) { + spaces(); + name = nexttoken; + id = identifier(); + if (funct['(global)'] && predefined[id] === false) { + warning("Redefinition of '{a}'.", token, id); + } + addlabel(id, 'unused'); + + if (nexttoken.id === '=') { + assign = nexttoken; + assign.first = name; + spaces(); + advance('='); + spaces(); + if (nexttoken.id === 'undefined') { + warning("It is not necessary to initialize '{a}' to 'undefined'.", token, id); + } + if (peek(0).id === '=' && nexttoken.identifier) { + error("Variable {a} was not declared correctly.", + nexttoken, nexttoken.value); + } + assign.second = expression(0); + assign.arity = 'infix'; + this.first.push(assign); + } else { + this.first.push(name); + } + if (nexttoken.id !== ',') { + break; + } + comma(); + } + return this; + }); + + stmt('function', function () { + one_space(); + if (inblock) { + warning( +"Function statements should not be placed in blocks. Use a function expression or move the statement to the top of the outer function.", token); + } + var i = identifier(); + if (i) { + addlabel(i, 'unction'); + no_space_only(); + } + doFunction(this, i, true); + if (nexttoken.id === '(' && nexttoken.line === token.line) { + error( +"Function statements are not invocable. Wrap the whole function invocation in parens."); + } + this.arity = 'statement'; + return this; + }); + + prefix('function', function () { + one_space(); + var i = optionalidentifier(); + if (i) { + no_space_only(); + } + doFunction(this, i); + if (funct['(loopage)']) { + warning("Don't make functions within a loop."); + } + this.arity = 'function'; + return this; + }); + + stmt('if', function () { + var t = nexttoken; + one_space(); + advance('('); + no_space(); + this.arity = 'statement'; + this.first = expression(20); + if (nexttoken.id === '=') { + warning("Expected a conditional expression and instead saw an assignment."); + advance('='); + expression(20); + } + no_space(); + advance(')', t); + one_space_only(); + this.block = block(true); + if (nexttoken.id === 'else') { + one_space(); + advance('else'); + one_space_only(); + this['else'] = nexttoken.id === 'if' || nexttoken.id === 'switch' ? + statement(true) : block(true); + if (this['else'].disrupt && this.block.disrupt) { + this.disrupt = true; + } + } + return this; + }); + + stmt('try', function () { + +// try.first The catch variable +// try.second The catch clause +// try.third The finally clause +// try.block The try block + + var b, e, s, t; + if (option.adsafe) { + warning("ADsafe try violation.", this); + } + one_space_only(); + this.arity = 'statement'; + this.block = block(false); + if (nexttoken.id === 'catch') { + one_space(); + advance('catch'); + one_space(); + advance('('); + no_space_only(); + s = scope; + scope = Object.create(s); + e = nexttoken.value; + this.first = e; + if (nexttoken.type !== '(identifier)') { + warning("Expected an identifier and instead saw '{a}'.", + nexttoken, e); + } else { + addlabel(e, 'exception'); + } + advance(); + no_space_only(); + advance(')'); + one_space(); + this.second = block(false); + b = true; + scope = s; + } + if (nexttoken.id === 'finally') { + one_space(); + t = nexttoken; + advance('finally'); + one_space(); + this.third = block(false); + } else if (!b) { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, 'catch', nexttoken.value); + } + return this; + }); + + + stmt('while', function () { + one_space(); + var t = nexttoken; + funct['(breakage)'] += 1; + funct['(loopage)'] += 1; + advance('('); + no_space(); + this.arity = 'statement'; + this.first = expression(20); + if (nexttoken.id === '=') { + warning("Expected a conditional expression and instead saw an assignment."); + advance('='); + expression(20); + } + no_space(); + advance(')', t); + one_space_only(); + this.block = block(true); + if (this.block.disrupt) { + warning("Strange loop.", prevtoken); + } + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + }); + + reserve('with'); + + stmt('switch', function () { + +// switch.first the switch expression +// switch.second the array of cases. A case is 'case' or 'default' token: +// case.first the array of case expressions +// case.second the array of statements +// If all of the arrays of statements are disrupt, then the switch is disrupt. + + var b = true, + s, + t = nexttoken; + funct['(breakage)'] += 1; + one_space(); + advance('('); + no_space(); + this.arity = 'statement'; + this.first = expression(20); + no_space(); + advance(')', t); + one_space_only(); + advance('{'); + this.second = []; + while (nexttoken.id === 'case') { + t = nexttoken; + t.first = []; + do { + spaces(); + advance('case'); + one_space(); + t.first.push(expression(0)); + no_space_only(); + advance(':'); + } while (nexttoken.id === 'case'); + spaces(); + t.second = statements(); + if (t.second && t.second.length > 0) { + s = t.second[t.second.length - 1]; + if (s.disrupt) { + if (s.id === 'break') { + b = false; + } + } else { + warning("Missing break after case."); + } + } else { + warning("Empty case"); + } + this.second.push(t); + } + if (this.second.length === 0) { + warning("switch without cases."); + } + if (nexttoken.id === 'default') { + spaces(); + t = nexttoken; + advance('default'); + no_space_only(); + advance(':'); + spaces(); + t.second = statements(); + if (t.second && t.second.length > 0) { + s = t.second[t.second.length - 1]; + if (b && s.disrupt && s.id !== 'break') { + this.disrupt = true; + } + } + this.second.push(t); + } + funct['(breakage)'] -= 1; + spaces(); + advance('}'); + return this; + }); + + stmt('debugger', function () { + if (!option.debug) { + warning("All 'debugger' statements should be removed."); + } + this.arity = 'statement'; + return this; + }); + + stmt('do', function () { + funct['(breakage)'] += 1; + funct['(loopage)'] += 1; + one_space_only(); + this.arity = 'statement'; + this.block = block(true); + if (this.block.disrupt) { + warning("Strange loop.", prevtoken); + } + one_space(); + advance('while'); + var t = nexttoken; + one_space_only(); + advance('('); + no_space(); + this.first = expression(0); + if (this.first.id === '=') { + warning("Expected a conditional expression and instead saw an assignment."); + } + no_space(); + advance(')', t); + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + }); + + stmt('for', function () { + var f = option.forin, i, s, t = nexttoken, v; + this.arity = 'statement'; + funct['(breakage)'] += 1; + funct['(loopage)'] += 1; + advance('('); + spaces(this, t); + no_space(); + if (nexttoken.id === 'var') { + error("Move all 'var' declarations to the top of the function."); + } + if (peek(0).id === 'in') { + v = nexttoken; + switch (funct[v.value]) { + case 'unused': + funct[v.value] = 'var'; + break; + case 'var': + break; + default: + warning("Bad for in variable '{a}'.", v, v.value); + } + advance(); + i = nexttoken; + advance('in'); + i.first = v; + i.second = expression(20); + advance(')', t); + this.first = i; + s = block(true); + if (!f && (s.length > 1 || typeof s[0] !== 'object' || + s[0].value !== 'if')) { + warning("The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.", this); + } + } else { + if (nexttoken.id !== ';') { + this.first = []; + for (;;) { + this.first.push(expression(0, 'for')); + if (nexttoken.id !== ',') { + break; + } + comma(); + } + } + semicolon(); + if (nexttoken.id !== ';') { + this.second = expression(20); + if (this.second.id === '=') { + warning("Expected a conditional expression and instead saw an assignment."); + } + } + semicolon(token); + if (nexttoken.id === ';') { + error("Expected '{a}' and instead saw '{b}'.", nexttoken, ')', ';'); + } + if (nexttoken.id !== ')') { + this.third = []; + for (;;) { + this.third.push(expression(0, 'for')); + if (nexttoken.id !== ',') { + break; + } + comma(); + } + } + no_space(); + advance(')', t); + one_space_only(); + s = block(true); + } + if (s.disrupt) { + warning("Strange loop.", prevtoken); + } + this.block = s; + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + }); + + + disruptstmt('break', function () { + var v = nexttoken.value; + this.arity = 'statement'; + if (funct['(breakage)'] === 0) { + warning("Unexpected '{a}'.", nexttoken, this.value); + } + if (nexttoken.identifier && token.line === nexttoken.line) { + one_space_only(); + if (funct[v] !== 'label') { + warning("'{a}' is not a label.", nexttoken, v); + } else if (scope[v] !== funct) { + warning("'{a}' is out of scope.", nexttoken, v); + } + this.first = nexttoken; + advance(); + } + return this; + }); + + + disruptstmt('continue', function () { + var v = nexttoken.value; + this.arity = 'statement'; + if (funct['(breakage)'] === 0) { + warning("Unexpected '{a}'.", nexttoken, this.value); + } + if (nexttoken.identifier && token.line === nexttoken.line) { + one_space_only(); + if (funct[v] !== 'label') { + warning("'{a}' is not a label.", nexttoken, v); + } else if (scope[v] !== funct) { + warning("'{a}' is out of scope.", nexttoken, v); + } + this.first = nexttoken; + advance(); + } + return this; + }); + + + disruptstmt('return', function () { + this.arity = 'statement'; + if (nexttoken.id !== ';' && nexttoken.line === token.line) { + one_space_only(); + if (nexttoken.id === '/' || nexttoken.id === '(regexp)') { + warning("Wrap the /regexp/ literal in parens to disambiguate the slash operator."); + } + this.first = expression(20); + } + return this; + }); + + + disruptstmt('throw', function () { + this.arity = 'statement'; + one_space_only(); + this.first = expression(20); + return this; + }); + + reserve('void'); + +// Superfluous reserved words + + reserve('class'); + reserve('const'); + reserve('enum'); + reserve('export'); + reserve('extends'); + reserve('import'); + reserve('super'); + +// Harmony reserved words + + reserve('let'); + reserve('yield'); + reserve('implements'); + reserve('interface'); + reserve('package'); + reserve('private'); + reserve('protected'); + reserve('public'); + reserve('static'); + + +// Parse JSON + + function jsonValue() { + + function jsonObject() { + var o = {}, t = nexttoken; + advance('{'); + if (nexttoken.id !== '}') { + for (;;) { + if (nexttoken.id === '(end)') { + error("Missing '}' to match '{' from line {a}.", + nexttoken, t.line); + } else if (nexttoken.id === '}') { + warning("Unexpected comma.", token); + break; + } else if (nexttoken.id === ',') { + error("Unexpected comma.", nexttoken); + } else if (nexttoken.id !== '(string)') { + warning("Expected a string and instead saw {a}.", + nexttoken, nexttoken.value); + } + if (o[nexttoken.value] === true) { + warning("Duplicate key '{a}'.", + nexttoken, nexttoken.value); + } else if (nexttoken.value === '__proto__') { + warning("Stupid key '{a}'.", + nexttoken, nexttoken.value); + } else { + o[nexttoken.value] = true; + } + advance(); + advance(':'); + jsonValue(); + if (nexttoken.id !== ',') { + break; + } + advance(','); + } + } + advance('}'); + } + + function jsonArray() { + var t = nexttoken; + advance('['); + if (nexttoken.id !== ']') { + for (;;) { + if (nexttoken.id === '(end)') { + error("Missing ']' to match '[' from line {a}.", + nexttoken, t.line); + } else if (nexttoken.id === ']') { + warning("Unexpected comma.", token); + break; + } else if (nexttoken.id === ',') { + error("Unexpected comma.", nexttoken); + } + jsonValue(); + if (nexttoken.id !== ',') { + break; + } + advance(','); + } + } + advance(']'); + } + + switch (nexttoken.id) { + case '{': + jsonObject(); + break; + case '[': + jsonArray(); + break; + case 'true': + case 'false': + case 'null': + case '(number)': + case '(string)': + advance(); + break; + case '-': + advance('-'); + if (token.thru !== nexttoken.from) { + warning("Unexpected space after '-'.", token); + } + no_space_only(); + advance('(number)'); + break; + default: + error("Expected a JSON value.", nexttoken); + } + } + + +// CSS parsing. + + function cssName() { + if (nexttoken.identifier) { + advance(); + return true; + } + } + + + function cssNumber() { + if (nexttoken.id === '-') { + advance('-'); + no_space_only(); + } + if (nexttoken.type === '(number)') { + advance('(number)'); + return true; + } + } + + + function cssString() { + if (nexttoken.type === '(string)') { + advance(); + return true; + } + } + + function cssColor() { + var i, number, value; + if (nexttoken.identifier) { + value = nexttoken.value; + if (value === 'rgb' || value === 'rgba') { + advance(); + advance('('); + for (i = 0; i < 3; i += 1) { + if (i) { + comma(); + } + number = nexttoken.value; + if (nexttoken.type !== '(number)' || number < 0) { + warning("Expected a positive number and instead saw '{a}'", + nexttoken, number); + advance(); + } else { + advance(); + if (nexttoken.id === '%') { + advance('%'); + if (number > 100) { + warning("Expected a percentage and instead saw '{a}'", + token, number); + } + } else { + if (number > 255) { + warning("Expected a small number and instead saw '{a}'", + token, number); + } + } + } + } + if (value === 'rgba') { + comma(); + number = +nexttoken.value; + if (nexttoken.type !== '(number)' || number < 0 || number > 1) { + warning("Expected a number between 0 and 1 and instead saw '{a}'", + nexttoken, number); + } + advance(); + if (nexttoken.id === '%') { + warning("Unexpected '%'."); + advance('%'); + } + } + advance(')'); + return true; + } else if (cssColorData[nexttoken.value] === true) { + advance(); + return true; + } + } else if (nexttoken.type === '(color)') { + advance(); + return true; + } + return false; + } + + + function cssLength() { + if (nexttoken.id === '-') { + advance('-'); + no_space_only(); + } + if (nexttoken.type === '(number)') { + advance(); + if (nexttoken.type !== '(string)' && + cssLengthData[nexttoken.value] === true) { + no_space_only(); + advance(); + } else if (+token.value !== 0) { + warning("Expected a linear unit and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + return true; + } + return false; + } + + + function cssLineHeight() { + if (nexttoken.id === '-') { + advance('-'); + no_space_only(); + } + if (nexttoken.type === '(number)') { + advance(); + if (nexttoken.type !== '(string)' && + cssLengthData[nexttoken.value] === true) { + no_space_only(); + advance(); + } + return true; + } + return false; + } + + + function cssWidth() { + if (nexttoken.identifier) { + switch (nexttoken.value) { + case 'thin': + case 'medium': + case 'thick': + advance(); + return true; + } + } else { + return cssLength(); + } + } + + + function cssMargin() { + if (nexttoken.identifier) { + if (nexttoken.value === 'auto') { + advance(); + return true; + } + } else { + return cssLength(); + } + } + + function cssAttr() { + if (nexttoken.identifier && nexttoken.value === 'attr') { + advance(); + advance('('); + if (!nexttoken.identifier) { + warning("Expected a name and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + advance(); + advance(')'); + return true; + } + return false; + } + + + function cssCommaList() { + while (nexttoken.id !== ';') { + if (!cssName() && !cssString()) { + warning("Expected a name and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + if (nexttoken.id !== ',') { + return true; + } + comma(); + } + } + + + function cssCounter() { + if (nexttoken.identifier && nexttoken.value === 'counter') { + advance(); + advance('('); + advance(); + if (nexttoken.id === ',') { + comma(); + if (nexttoken.type !== '(string)') { + warning("Expected a string and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + advance(); + } + advance(')'); + return true; + } + if (nexttoken.identifier && nexttoken.value === 'counters') { + advance(); + advance('('); + if (!nexttoken.identifier) { + warning("Expected a name and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + advance(); + if (nexttoken.id === ',') { + comma(); + if (nexttoken.type !== '(string)') { + warning("Expected a string and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + advance(); + } + if (nexttoken.id === ',') { + comma(); + if (nexttoken.type !== '(string)') { + warning("Expected a string and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + advance(); + } + advance(')'); + return true; + } + return false; + } + + + function cssShape() { + var i; + if (nexttoken.identifier && nexttoken.value === 'rect') { + advance(); + advance('('); + for (i = 0; i < 4; i += 1) { + if (!cssLength()) { + warning("Expected a number and instead saw '{a}'.", + nexttoken, nexttoken.value); + break; + } + } + advance(')'); + return true; + } + return false; + } + + + function cssUrl() { + var c, url; + if (nexttoken.identifier && nexttoken.value === 'url') { + nexttoken = lex.range('(', ')'); + url = nexttoken.value; + c = url.charAt(0); + if (c === '"' || c === '\'') { + if (url.slice(-1) !== c) { + warning("Bad url string."); + } else { + url = url.slice(1, -1); + if (url.indexOf(c) >= 0) { + warning("Bad url string."); + } + } + } + if (!url) { + warning("Missing url."); + } + advance(); + if (option.safe && ux.test(url)) { + error("ADsafe URL violation."); + } + urls.push(url); + return true; + } + return false; + } + + + cssAny = [cssUrl, function () { + for (;;) { + if (nexttoken.identifier) { + switch (nexttoken.value.toLowerCase()) { + case 'url': + cssUrl(); + break; + case 'expression': + warning("Unexpected expression '{a}'.", + nexttoken, nexttoken.value); + advance(); + break; + default: + advance(); + } + } else { + if (nexttoken.id === ';' || nexttoken.id === '!' || + nexttoken.id === '(end)' || nexttoken.id === '}') { + return true; + } + advance(); + } + } + }]; + + + cssBorderStyle = [ + 'none', 'dashed', 'dotted', 'double', 'groove', + 'hidden', 'inset', 'outset', 'ridge', 'solid' + ]; + + cssBreak = [ + 'auto', 'always', 'avoid', 'left', 'right' + ]; + + cssMedia = { + 'all': true, + 'braille': true, + 'embossed': true, + 'handheld': true, + 'print': true, + 'projection': true, + 'screen': true, + 'speech': true, + 'tty': true, + 'tv': true + }; + + cssOverflow = [ + 'auto', 'hidden', 'scroll', 'visible' + ]; + + cssAttributeData = { + background: [ + true, 'background-attachment', 'background-color', + 'background-image', 'background-position', 'background-repeat' + ], + 'background-attachment': ['scroll', 'fixed'], + 'background-color': ['transparent', cssColor], + 'background-image': ['none', cssUrl], + 'background-position': [ + 2, [cssLength, 'top', 'bottom', 'left', 'right', 'center'] + ], + 'background-repeat': [ + 'repeat', 'repeat-x', 'repeat-y', 'no-repeat' + ], + 'border': [true, 'border-color', 'border-style', 'border-width'], + 'border-bottom': [ + true, 'border-bottom-color', 'border-bottom-style', + 'border-bottom-width' + ], + 'border-bottom-color': cssColor, + 'border-bottom-style': cssBorderStyle, + 'border-bottom-width': cssWidth, + 'border-collapse': ['collapse', 'separate'], + 'border-color': ['transparent', 4, cssColor], + 'border-left': [ + true, 'border-left-color', 'border-left-style', 'border-left-width' + ], + 'border-left-color': cssColor, + 'border-left-style': cssBorderStyle, + 'border-left-width': cssWidth, + 'border-right': [ + true, 'border-right-color', 'border-right-style', + 'border-right-width' + ], + 'border-right-color': cssColor, + 'border-right-style': cssBorderStyle, + 'border-right-width': cssWidth, + 'border-spacing': [2, cssLength], + 'border-style': [4, cssBorderStyle], + 'border-top': [ + true, 'border-top-color', 'border-top-style', 'border-top-width' + ], + 'border-top-color': cssColor, + 'border-top-style': cssBorderStyle, + 'border-top-width': cssWidth, + 'border-width': [4, cssWidth], + bottom: [cssLength, 'auto'], + 'caption-side' : ['bottom', 'left', 'right', 'top'], + clear: ['both', 'left', 'none', 'right'], + clip: [cssShape, 'auto'], + color: cssColor, + content: [ + 'open-quote', 'close-quote', 'no-open-quote', 'no-close-quote', + cssString, cssUrl, cssCounter, cssAttr + ], + 'counter-increment': [ + cssName, 'none' + ], + 'counter-reset': [ + cssName, 'none' + ], + cursor: [ + cssUrl, 'auto', 'crosshair', 'default', 'e-resize', 'help', 'move', + 'n-resize', 'ne-resize', 'nw-resize', 'pointer', 's-resize', + 'se-resize', 'sw-resize', 'w-resize', 'text', 'wait' + ], + direction: ['ltr', 'rtl'], + display: [ + 'block', 'compact', 'inline', 'inline-block', 'inline-table', + 'list-item', 'marker', 'none', 'run-in', 'table', 'table-caption', + 'table-cell', 'table-column', 'table-column-group', + 'table-footer-group', 'table-header-group', 'table-row', + 'table-row-group' + ], + 'empty-cells': ['show', 'hide'], + 'float': ['left', 'none', 'right'], + font: [ + 'caption', 'icon', 'menu', 'message-box', 'small-caption', + 'status-bar', true, 'font-size', 'font-style', 'font-weight', + 'font-family' + ], + 'font-family': cssCommaList, + 'font-size': [ + 'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', + 'xx-large', 'larger', 'smaller', cssLength + ], + 'font-size-adjust': ['none', cssNumber], + 'font-stretch': [ + 'normal', 'wider', 'narrower', 'ultra-condensed', + 'extra-condensed', 'condensed', 'semi-condensed', + 'semi-expanded', 'expanded', 'extra-expanded' + ], + 'font-style': [ + 'normal', 'italic', 'oblique' + ], + 'font-variant': [ + 'normal', 'small-caps' + ], + 'font-weight': [ + 'normal', 'bold', 'bolder', 'lighter', cssNumber + ], + height: [cssLength, 'auto'], + left: [cssLength, 'auto'], + 'letter-spacing': ['normal', cssLength], + 'line-height': ['normal', cssLineHeight], + 'list-style': [ + true, 'list-style-image', 'list-style-position', 'list-style-type' + ], + 'list-style-image': ['none', cssUrl], + 'list-style-position': ['inside', 'outside'], + 'list-style-type': [ + 'circle', 'disc', 'square', 'decimal', 'decimal-leading-zero', + 'lower-roman', 'upper-roman', 'lower-greek', 'lower-alpha', + 'lower-latin', 'upper-alpha', 'upper-latin', 'hebrew', 'katakana', + 'hiragana-iroha', 'katakana-oroha', 'none' + ], + margin: [4, cssMargin], + 'margin-bottom': cssMargin, + 'margin-left': cssMargin, + 'margin-right': cssMargin, + 'margin-top': cssMargin, + 'marker-offset': [cssLength, 'auto'], + 'max-height': [cssLength, 'none'], + 'max-width': [cssLength, 'none'], + 'min-height': cssLength, + 'min-width': cssLength, + opacity: cssNumber, + outline: [true, 'outline-color', 'outline-style', 'outline-width'], + 'outline-color': ['invert', cssColor], + 'outline-style': [ + 'dashed', 'dotted', 'double', 'groove', 'inset', 'none', + 'outset', 'ridge', 'solid' + ], + 'outline-width': cssWidth, + overflow: cssOverflow, + 'overflow-x': cssOverflow, + 'overflow-y': cssOverflow, + padding: [4, cssLength], + 'padding-bottom': cssLength, + 'padding-left': cssLength, + 'padding-right': cssLength, + 'padding-top': cssLength, + 'page-break-after': cssBreak, + 'page-break-before': cssBreak, + position: ['absolute', 'fixed', 'relative', 'static'], + quotes: [8, cssString], + right: [cssLength, 'auto'], + 'table-layout': ['auto', 'fixed'], + 'text-align': ['center', 'justify', 'left', 'right'], + 'text-decoration': [ + 'none', 'underline', 'overline', 'line-through', 'blink' + ], + 'text-indent': cssLength, + 'text-shadow': ['none', 4, [cssColor, cssLength]], + 'text-transform': ['capitalize', 'uppercase', 'lowercase', 'none'], + top: [cssLength, 'auto'], + 'unicode-bidi': ['normal', 'embed', 'bidi-override'], + 'vertical-align': [ + 'baseline', 'bottom', 'sub', 'super', 'top', 'text-top', 'middle', + 'text-bottom', cssLength + ], + visibility: ['visible', 'hidden', 'collapse'], + 'white-space': [ + 'normal', 'nowrap', 'pre', 'pre-line', 'pre-wrap', 'inherit' + ], + width: [cssLength, 'auto'], + 'word-spacing': ['normal', cssLength], + 'word-wrap': ['break-word', 'normal'], + 'z-index': ['auto', cssNumber] + }; + + function styleAttribute() { + var v; + while (nexttoken.id === '*' || nexttoken.id === '#' || + nexttoken.value === '_') { + if (!option.css) { + warning("Unexpected '{a}'.", nexttoken, nexttoken.value); + } + advance(); + } + if (nexttoken.id === '-') { + if (!option.css) { + warning("Unexpected '{a}'.", nexttoken, nexttoken.value); + } + advance('-'); + if (!nexttoken.identifier) { + warning( +"Expected a non-standard style attribute and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + advance(); + return cssAny; + } else { + if (!nexttoken.identifier) { + warning("Excepted a style attribute, and instead saw '{a}'.", + nexttoken, nexttoken.value); + } else { + if (is_own(cssAttributeData, nexttoken.value)) { + v = cssAttributeData[nexttoken.value]; + } else { + v = cssAny; + if (!option.css) { + warning("Unrecognized style attribute '{a}'.", + nexttoken, nexttoken.value); + } + } + } + advance(); + return v; + } + } + + + function styleValue(v) { + var i = 0, + n, + once, + match, + round, + start = 0, + vi; + switch (typeof v) { + case 'function': + return v(); + case 'string': + if (nexttoken.identifier && nexttoken.value === v) { + advance(); + return true; + } + return false; + } + for (;;) { + if (i >= v.length) { + return false; + } + vi = v[i]; + i += 1; + if (vi === true) { + break; + } else if (typeof vi === 'number') { + n = vi; + vi = v[i]; + i += 1; + } else { + n = 1; + } + match = false; + while (n > 0) { + if (styleValue(vi)) { + match = true; + n -= 1; + } else { + break; + } + } + if (match) { + return true; + } + } + start = i; + once = []; + for (;;) { + round = false; + for (i = start; i < v.length; i += 1) { + if (!once[i]) { + if (styleValue(cssAttributeData[v[i]])) { + match = true; + round = true; + once[i] = true; + break; + } + } + } + if (!round) { + return match; + } + } + } + + function styleChild() { + if (nexttoken.id === '(number)') { + advance(); + if (nexttoken.value === 'n' && nexttoken.identifier) { + no_space_only(); + advance(); + if (nexttoken.id === '+') { + no_space_only(); + advance('+'); + no_space_only(); + advance('(number)'); + } + } + return; + } else { + if (nexttoken.identifier && + (nexttoken.value === 'odd' || nexttoken.value === 'even')) { + advance(); + return; + } + } + warning("Unexpected token '{a}'.", nexttoken, nexttoken.value); + } + + function substyle() { + var v; + for (;;) { + if (nexttoken.id === '}' || nexttoken.id === '(end)' || + xquote && nexttoken.id === xquote) { + return; + } + while (nexttoken.id === ';') { + warning("Misplaced ';'."); + advance(';'); + } + v = styleAttribute(); + advance(':'); + if (nexttoken.identifier && nexttoken.value === 'inherit') { + advance(); + } else { + if (!styleValue(v)) { + warning("Unexpected token '{a}'.", nexttoken, + nexttoken.value); + advance(); + } + } + if (nexttoken.id === '!') { + advance('!'); + no_space_only(); + if (nexttoken.identifier && nexttoken.value === 'important') { + advance(); + } else { + warning("Expected '{a}' and instead saw '{b}'.", + nexttoken, 'important', nexttoken.value); + } + } + if (nexttoken.id === '}' || nexttoken.id === xquote) { + warning("Missing '{a}'.", nexttoken, ';'); + } else { + semicolon(); + } + } + } + + function styleSelector() { + if (nexttoken.identifier) { + if (!is_own(htmltag, option.cap ? + nexttoken.value.toLowerCase() : nexttoken.value)) { + warning("Expected a tagName, and instead saw {a}.", + nexttoken, nexttoken.value); + } + advance(); + } else { + switch (nexttoken.id) { + case '>': + case '+': + advance(); + styleSelector(); + break; + case ':': + advance(':'); + switch (nexttoken.value) { + case 'active': + case 'after': + case 'before': + case 'checked': + case 'disabled': + case 'empty': + case 'enabled': + case 'first-child': + case 'first-letter': + case 'first-line': + case 'first-of-type': + case 'focus': + case 'hover': + case 'last-child': + case 'last-of-type': + case 'link': + case 'only-of-type': + case 'root': + case 'target': + case 'visited': + advance(); + break; + case 'lang': + advance(); + advance('('); + if (!nexttoken.identifier) { + warning("Expected a lang code, and instead saw :{a}.", + nexttoken, nexttoken.value); + } + advance(')'); + break; + case 'nth-child': + case 'nth-last-child': + case 'nth-last-of-type': + case 'nth-of-type': + advance(); + advance('('); + styleChild(); + advance(')'); + break; + case 'not': + advance(); + advance('('); + if (nexttoken.id === ':' && peek(0).value === 'not') { + warning("Nested not."); + } + styleSelector(); + advance(')'); + break; + default: + warning("Expected a pseudo, and instead saw :{a}.", + nexttoken, nexttoken.value); + } + break; + case '#': + advance('#'); + if (!nexttoken.identifier) { + warning("Expected an id, and instead saw #{a}.", + nexttoken, nexttoken.value); + } + advance(); + break; + case '*': + advance('*'); + break; + case '.': + advance('.'); + if (!nexttoken.identifier) { + warning("Expected a class, and instead saw #.{a}.", + nexttoken, nexttoken.value); + } + advance(); + break; + case '[': + advance('['); + if (!nexttoken.identifier) { + warning("Expected an attribute, and instead saw [{a}].", + nexttoken, nexttoken.value); + } + advance(); + if (nexttoken.id === '=' || nexttoken.value === '~=' || + nexttoken.value === '$=' || + nexttoken.value === '|=' || + nexttoken.id === '*=' || + nexttoken.id === '^=') { + advance(); + if (nexttoken.type !== '(string)') { + warning("Expected a string, and instead saw {a}.", + nexttoken, nexttoken.value); + } + advance(); + } + advance(']'); + break; + default: + error("Expected a CSS selector, and instead saw {a}.", + nexttoken, nexttoken.value); + } + } + } + + function stylePattern() { + if (nexttoken.id === '{') { + warning("Expected a style pattern, and instead saw '{a}'.", nexttoken, + nexttoken.id); + } + for (;;) { + styleSelector(); + if (nexttoken.id === ' fragments and .js files.", token); + } + if (option.fragment) { + if (n !== 'div') { + error("ADsafe violation: Wrap the widget in a div.", token); + } + } else { + error("Use the fragment option.", token); + } + } + option.browser = true; + assume(); + } + + function doAttribute(n, a, v) { + var u, x; + if (a === 'id') { + u = typeof v === 'string' ? v.toUpperCase() : ''; + if (ids[u] === true) { + warning("Duplicate id='{a}'.", nexttoken, v); + } + if (!/^[A-Za-z][A-Za-z0-9._:\-]*$/.test(v)) { + warning("Bad id: '{a}'.", nexttoken, v); + } else if (option.adsafe) { + if (adsafe_id) { + if (v.slice(0, adsafe_id.length) !== adsafe_id) { + warning("ADsafe violation: An id must have a '{a}' prefix", + nexttoken, adsafe_id); + } else if (!/^[A-Z]+_[A-Z]+$/.test(v)) { + warning("ADSAFE violation: bad id."); + } + } else { + adsafe_id = v; + if (!/^[A-Z]+_$/.test(v)) { + warning("ADSAFE violation: bad id."); + } + } + } + x = v.search(dx); + if (x >= 0) { + warning("Unexpected character '{a}' in {b}.", token, v.charAt(x), a); + } + ids[u] = true; + } else if (a === 'class' || a === 'type' || a === 'name') { + x = v.search(qx); + if (x >= 0) { + warning("Unexpected character '{a}' in {b}.", token, v.charAt(x), a); + } + ids[u] = true; + } else if (a === 'href' || a === 'background' || + a === 'content' || a === 'data' || + a.indexOf('src') >= 0 || a.indexOf('url') >= 0) { + if (option.safe && ux.test(v)) { + error("ADsafe URL violation."); + } + urls.push(v); + } else if (a === 'for') { + if (option.adsafe) { + if (adsafe_id) { + if (v.slice(0, adsafe_id.length) !== adsafe_id) { + warning("ADsafe violation: An id must have a '{a}' prefix", + nexttoken, adsafe_id); + } else if (!/^[A-Z]+_[A-Z]+$/.test(v)) { + warning("ADSAFE violation: bad id."); + } + } else { + warning("ADSAFE violation: bad id."); + } + } + } else if (a === 'name') { + if (option.adsafe && v.indexOf('_') >= 0) { + warning("ADsafe name violation."); + } + } + } + + function doTag(n, a) { + var i, t = htmltag[n], x; + src = false; + if (!t) { + error("Unrecognized tag '<{a}>'.", + nexttoken, + n === n.toLowerCase() ? n : + n + ' (capitalization error)'); + } + if (stack.length > 0) { + if (n === 'html') { + error("Too many tags.", token); + } + x = t.parent; + if (x) { + if (x.indexOf(' ' + stack[stack.length - 1].name + ' ') < 0) { + error("A '<{a}>' must be within '<{b}>'.", + token, n, x); + } + } else if (!option.adsafe && !option.fragment) { + i = stack.length; + do { + if (i <= 0) { + error("A '<{a}>' must be within '<{b}>'.", + token, n, 'body'); + } + i -= 1; + } while (stack[i].name !== 'body'); + } + } + switch (n) { + case 'div': + if (option.adsafe && stack.length === 1 && !adsafe_id) { + warning("ADSAFE violation: missing ID_."); + } + break; + case 'script': + xmode = 'script'; + advance('>'); + ////indent = nexttoken.from; + if (a.lang) { + warning("lang is deprecated.", token); + } + if (option.adsafe && stack.length !== 1) { + warning("ADsafe script placement violation.", token); + } + if (a.src) { + if (option.adsafe && (!adsafe_may || !approved[a.src])) { + warning("ADsafe unapproved script source.", token); + } + if (a.type) { + warning("type is unnecessary.", token); + } + } else { + if (adsafe_went) { + error("ADsafe script violation.", token); + } + use_strict(); + statements('script'); + } + xmode = 'html'; + advance(''); + styles(); + xmode = 'html'; + advance(''; + } + + function html() { + var a, attributes, e, n, q, t, v, w = option.white, wmode; + xmode = 'html'; + xquote = ''; + stack = null; + for (;;) { + switch (nexttoken.value) { + case '<': + xmode = 'html'; + advance('<'); + attributes = {}; + t = nexttoken; + if (!t.identifier) { + warning("Bad identifier {a}.", t, t.value); + } + n = t.value; + if (option.cap) { + n = n.toLowerCase(); + } + t.name = n; + advance(); + if (!stack) { + stack = []; + doBegin(n); + } + v = htmltag[n]; + if (typeof v !== 'object') { + error("Unrecognized tag '<{a}>'.", t, n); + } + e = v.empty; + t.type = n; + for (;;) { + if (nexttoken.id === '/') { + advance('/'); + if (nexttoken.id !== '>') { + warning("Expected '{a}' and instead saw '{b}'.", + nexttoken, '>', nexttoken.value); + } + break; + } + if (nexttoken.id && nexttoken.id.substr(0, 1) === '>') { + break; + } + if (!nexttoken.identifier) { + if (nexttoken.id === '(end)' || nexttoken.id === '(error)') { + error("Missing '>'.", nexttoken); + } + warning("Bad identifier."); + } + option.white = true; + spaces(); + a = nexttoken.value; + option.white = w; + advance(); + if (!option.cap && a !== a.toLowerCase()) { + warning("Attribute '{a}' not all lower case.", nexttoken, a); + } + a = a.toLowerCase(); + xquote = ''; + if (is_own(attributes, a)) { + warning("Attribute '{a}' repeated.", nexttoken, a); + } + if (a.slice(0, 2) === 'on') { + if (!option.on) { + warning("Avoid HTML event handlers."); + } + xmode = 'scriptstring'; + advance('='); + q = nexttoken.id; + if (q !== '"' && q !== "'") { + error("Missing quote."); + } + xquote = q; + wmode = option.white; + option.white = false; + advance(q); + use_strict(); + statements('on'); + option.white = wmode; + if (nexttoken.id !== q) { + error("Missing close quote on script attribute."); + } + xmode = 'html'; + xquote = ''; + advance(q); + v = false; + } else if (a === 'style') { + xmode = 'scriptstring'; + advance('='); + q = nexttoken.id; + if (q !== '"' && q !== "'") { + error("Missing quote."); + } + xmode = 'styleproperty'; + xquote = q; + advance(q); + substyle(); + xmode = 'html'; + xquote = ''; + advance(q); + v = false; + } else { + if (nexttoken.id === '=') { + advance('='); + v = nexttoken.value; + if (!nexttoken.identifier && + nexttoken.id !== '"' && + nexttoken.id !== '\'' && + nexttoken.type !== '(string)' && + nexttoken.type !== '(number)' && + nexttoken.type !== '(color)') { + warning("Expected an attribute value and instead saw '{a}'.", token, a); + } + advance(); + } else { + v = true; + } + } + attributes[a] = v; + doAttribute(n, a, v); + } + doTag(n, attributes); + if (!e) { + stack.push(t); + } + xmode = 'outer'; + advance('>'); + break; + case '') { + error("Missing '{a}'.", nexttoken, '>'); + } + xmode = 'outer'; + advance('>'); + break; + case '' || nexttoken.id === '(end)') { + break; + } + if (nexttoken.value.indexOf('--') >= 0) { + error("Unexpected --."); + } + if (nexttoken.value.indexOf('<') >= 0) { + error("Unexpected <."); + } + if (nexttoken.value.indexOf('>') >= 0) { + error("Unexpected >."); + } + } + xmode = 'outer'; + advance('>'); + break; + case '(end)': + return; + default: + if (nexttoken.id === '(end)') { + error("Missing '{a}'.", nexttoken, + ''); + } else { + advance(); + } + } + if (stack && stack.length === 0 && (option.adsafe || + !option.fragment || nexttoken.id === '(end)')) { + break; + } + } + if (nexttoken.id !== '(end)') { + error("Unexpected material after the end."); + } + } + + +// The actual JSLINT function itself. + + var itself = function (s, o) { + var a, i, k; + JSLINT.errors = []; + JSLINT.tree = ''; + predefined = Object.create(standard); + if (o) { + a = o.predef; + if (a) { + if (Array.isArray(a)) { + for (i = 0; i < a.length; i += 1) { + predefined[a[i]] = true; + } + } else if (typeof a === 'object') { + k = Object.keys(a); + for (i = 0; i < k.length; i += 1) { + predefined[k[i]] = !!a[k]; + } + } + } + if (o.adsafe) { + o.safe = true; + } + if (o.safe) { + o.browser = + o.css = + o.debug = + o.devel = + o.evil = + o.forin = + o.on = + o.rhino = + o.windows = + o.sub = + o.widget = false; + + o.nomen = + o.safe = + o.undef = true; + + predefined.Date = + predefined['eval'] = + predefined.Function = + predefined.Object = null; + + predefined.ADSAFE = + predefined.lib = false; + } + option = o; + } else { + option = {}; + } + option.indent = option.indent || 4; + option.maxerr = option.maxerr || 50; + adsafe_id = ''; + adsafe_may = false; + adsafe_went = false; + approved = {}; + if (option.approved) { + for (i = 0; i < option.approved.length; i += 1) { + approved[option.approved[i]] = option.approved[i]; + } + } else { + approved.test = 'test'; + } + tab = ''; + for (i = 0; i < option.indent; i += 1) { + tab += ' '; + } + global = Object.create(predefined); + scope = global; + funct = { + '(global)': true, + '(name)': '(global)', + '(scope)': scope, + '(breakage)': 0, + '(loopage)': 0 + }; + functions = [funct]; + ids = {}; + urls = []; + src = false; + xmode = false; + stack = null; + member = {}; + membersOnly = null; + implied = {}; + inblock = false; + lookahead = []; + jsonmode = false; + warnings = 0; + lex.init(s); + prereg = true; + strict_mode = false; + + prevtoken = token = nexttoken = syntax['(begin)']; + assume(); + + try { + advance(); + if (nexttoken.value.charAt(0) === '<') { + html(); + if (option.adsafe && !adsafe_went) { + warning("ADsafe violation: Missing ADSAFE.go.", this); + } + } else { + switch (nexttoken.id) { + case '{': + case '[': + jsonmode = true; + jsonValue(); + break; + case '@': + case '*': + case '#': + case '.': + case ':': + xmode = 'style'; + advance(); + if (token.id !== '@' || !nexttoken.identifier || + nexttoken.value !== 'charset' || token.line !== 1 || + token.from !== 1) { + error("A css file should begin with @charset 'UTF-8';"); + } + advance(); + if (nexttoken.type !== '(string)' && + nexttoken.value !== 'UTF-8') { + error("A css file should begin with @charset 'UTF-8';"); + } + advance(); + semicolon(); + styles(); + break; + + default: + if (option.adsafe && option.fragment) { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, '
', nexttoken.value); + } + if (nexttoken.value === 'use strict') { + warning("Use the function form of \"use strict\"."); + use_strict(); + } + JSLINT.tree = statements('lib'); + if (JSLINT.tree.disrupt) { + warning("Weird program.", prevtoken); + } + } + } + advance('(end)'); + } catch (e) { + if (e) { + JSLINT.errors.push({ + reason : e.message, + line : e.line || nexttoken.line, + character : e.character || nexttoken.from + }, null); + } + } + return JSLINT.errors.length === 0; + }; + + +// Data summary. + + itself.data = function () { + + var data = {functions: []}, fu, globals, implieds = [], f, i, j, + members = [], n, unused = [], v; + if (itself.errors.length) { + data.errors = itself.errors; + } + + if (jsonmode) { + data.json = true; + } + + for (n in implied) { + if (is_own(implied, n)) { + implieds.push({ + name: n, + line: implied[n] + }); + } + } + if (implieds.length > 0) { + data.implieds = implieds; + } + + if (urls.length > 0) { + data.urls = urls; + } + + globals = Object.keys(scope); + if (globals.length > 0) { + data.globals = globals; + } + + for (i = 1; i < functions.length; i += 1) { + f = functions[i]; + fu = {}; + for (j = 0; j < functionicity.length; j += 1) { + fu[functionicity[j]] = []; + } + for (n in f) { + if (is_own(f, n) && n.charAt(0) !== '(') { + v = f[n]; + if (v === 'unction') { + v = 'unused'; + } + if (Array.isArray(fu[v])) { + fu[v].push(n); + if (v === 'unused') { + unused.push({ + name: n, + line: f['(line)'], + 'function': f['(name)'] + }); + } + } + } + } + for (j = 0; j < functionicity.length; j += 1) { + if (fu[functionicity[j]].length === 0) { + delete fu[functionicity[j]]; + } + } + fu.name = f['(name)']; + fu.param = f['(params)']; + fu.line = f['(line)']; + fu.last = f['(last)']; + data.functions.push(fu); + } + + if (unused.length > 0) { + data.unused = unused; + } + + members = []; + for (n in member) { + if (typeof member[n] === 'number') { + data.member = member; + break; + } + } + + return data; + }; + + itself.report = function (option) { + var data = itself.data(); + + var a = [], c, e, err, f, i, k, l, m = '', n, o = [], s; + + function detail(h, array) { + var b, i, singularity; + if (array) { + o.push('
' + h + ' '); + array = array.sort(); + for (i = 0; i < array.length; i += 1) { + if (array[i] !== singularity) { + singularity = array[i]; + o.push((b ? ', ' : '') + singularity); + b = true; + } + } + o.push('
'); + } + } + + if (data.errors || data.implieds || data.unused) { + err = true; + o.push('
Error:'); + if (data.errors) { + for (i = 0; i < data.errors.length; i += 1) { + c = data.errors[i]; + if (c) { + e = c.evidence || ''; + o.push('

Problem' + (isFinite(c.line) ? ' at line ' + + c.line + ' character ' + c.character : '') + + ': ' + c.reason.entityify() + + '

' + + (e && (e.length > 80 ? e.slice(0, 77) + '...' : + e).entityify()) + '

'); + } + } + } + + if (data.implieds) { + s = []; + for (i = 0; i < data.implieds.length; i += 1) { + s[i] = '' + data.implieds[i].name + ' ' + + data.implieds[i].line + ''; + } + o.push('

Implied global: ' + s.join(', ') + '

'); + } + + if (data.unused) { + s = []; + for (i = 0; i < data.unused.length; i += 1) { + s[i] = '' + data.unused[i].name + ' ' + + data.unused[i].line + ' ' + + data.unused[i]['function'] + ''; + } + o.push('

Unused variable: ' + s.join(', ') + '

'); + } + if (data.json) { + o.push('

JSON: bad.

'); + } + o.push('
'); + } + + if (!option) { + + o.push('
'); + + if (data.urls) { + detail("URLs
", data.urls, '
'); + } + + if (xmode === 'style') { + o.push('

CSS.

'); + } else if (data.json && !err) { + o.push('

JSON: good.

'); + } else if (data.globals) { + o.push('
Global ' + + data.globals.sort().join(', ') + '
'); + } else { + o.push('
No new global variables introduced.
'); + } + + for (i = 0; i < data.functions.length; i += 1) { + f = data.functions[i]; + + o.push('
' + f.line + '-' + + f.last + ' ' + (f.name || '') + '(' + + (f.param ? f.param.join(', ') : '') + ')
'); + detail('Unused', f.unused); + detail('Closure', f.closure); + detail('Variable', f['var']); + detail('Exception', f.exception); + detail('Outer', f.outer); + detail('Global', f.global); + detail('Label', f.label); + } + + if (data.member) { + a = Object.keys(data.member); + if (a.length) { + a = a.sort(); + m = '
/*members ';
+                    l = 10;
+                    for (i = 0; i < a.length; i += 1) {
+                        k = a[i];
+                        n = k.name();
+                        if (l + n.length > 72) {
+                            o.push(m + '
'); + m = ' '; + l = 1; + } + l += n.length + 2; + if (data.member[k] === 1) { + n = '' + n + ''; + } + if (i < a.length - 1) { + n += ', '; + } + m += n; + } + o.push(m + '
*/
'); + } + o.push('
'); + } + } + return o.join(''); + }; + itself.jslint = itself; + + itself.edition = '2011-01-09'; + + return itself; + +}()); + +}); \ No newline at end of file From 1e4ee57d6306f3841cd0936e754dbfb004450193 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Sat, 22 Jan 2011 21:24:05 +0100 Subject: [PATCH 06/29] add jslint example --- demo/startup.js | 36 ++++++++++++++++-- lib/ace/css/editor.css | 4 ++ lib/ace/worker/host.js | 22 +++++------ lib/ace/worker/jslint.js | 10 ++--- lib/ace/worker/mirror.js | 38 +++++++++++++++++++ .../{WorkerClient.js => worker_client.js} | 16 +++++--- 6 files changed, 101 insertions(+), 25 deletions(-) create mode 100644 lib/ace/worker/mirror.js rename lib/ace/worker/{WorkerClient.js => worker_client.js} (84%) diff --git a/demo/startup.js b/demo/startup.js index 419e2667..49581563 100644 --- a/demo/startup.js +++ b/demo/startup.js @@ -59,7 +59,7 @@ exports.launch = function(env) { var emacs = require("ace/keyboard/keybinding/emacs").Emacs; var HashHandler = require("ace/keyboard/hash_handler").HashHandler; - var WorkerClient = require("ace/worker/WorkerClient").WorkerClient; + var WorkerClient = require("ace/worker/worker_client").WorkerClient; var docs = {}; @@ -67,9 +67,37 @@ exports.launch = function(env) { docs.js.setMode(new JavaScriptMode()); docs.js.setUndoManager(new UndoManager()); - var worker = new WorkerClient("../..", ["ace", "pilot"], "ace/worker/demo", "Demo"); - worker.send("juhu"); - + 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) { + console.log("jslint", results); + + var rows = []; + for (var i=0; i Date: Sat, 22 Jan 2011 23:58:03 +0100 Subject: [PATCH 07/29] update jslint demo --- demo/startup.js | 17 ++++++++++------- lib/ace/css/editor.css | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/demo/startup.js b/demo/startup.js index 49581563..5778e6a0 100644 --- a/demo/startup.js +++ b/demo/startup.js @@ -79,17 +79,20 @@ exports.launch = function(env) { }); worker.on("jslint", function(results) { - console.log("jslint", results); - - var rows = []; + var errors = []; for (var i=0; i Date: Sun, 23 Jan 2011 00:01:29 +0100 Subject: [PATCH 08/29] add support for line annotations --- lib/ace/edit_session.js | 35 +++++++++++++++++++++++++++++++++++ lib/ace/editor.js | 9 +++++++++ lib/ace/layer/gutter.js | 36 ++++++++++++++++++++++++++++++++---- lib/ace/virtual_renderer.js | 5 +++++ 4 files changed, 81 insertions(+), 4 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index e9e2a66a..c0c3c6ad 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -198,6 +198,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", (i+1), "
"); html.push(""); } diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index 687085fa..affce7fb 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -474,6 +474,11 @@ var VirtualRenderer = function(container, theme) { this.$loop.schedule(this.CHANGE_GUTTER); }; + this.setAnnotations = function(annotations) { + this.$gutterLayer.setAnnotations(annotations); + this.$loop.schedule(this.CHANGE_GUTTER); + }; + this.updateCursor = function(position, overwrite) { this.$cursorLayer.setCursor(position, overwrite); this.$loop.schedule(this.CHANGE_CURSOR); From b9e26f2b8dbcb6e308fe7bd88a3461cf0a7462f7 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Sun, 23 Jan 2011 14:26:06 +0100 Subject: [PATCH 09/29] move css to an external file --- editor.html | 61 +---------------------------------------------------- 1 file changed, 1 insertion(+), 60 deletions(-) diff --git a/editor.html b/editor.html index f1b20bf5..30e67abb 100644 --- a/editor.html +++ b/editor.html @@ -6,66 +6,7 @@ Editor - - +
From 30975d5b0d567dcc03c51cea8bff06edc87b72b2 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Sun, 23 Jan 2011 14:26:29 +0100 Subject: [PATCH 10/29] remove error decoration --- lib/ace/css/editor.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/ace/css/editor.css b/lib/ace/css/editor.css index 2d79f9db..cd537ddd 100644 --- a/lib/ace/css/editor.css +++ b/lib/ace/css/editor.css @@ -19,10 +19,6 @@ height: 100%; } -.ace_gutter-cell.ace_error { - color: red; -} - .ace_editor .ace_sb { position: absolute; overflow-x: hidden; From 8238e36ca1a62278ed6b660ae19e53d56b4c3bc9 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Sun, 23 Jan 2011 14:59:53 +0100 Subject: [PATCH 11/29] nicely integrate the worker code --- demo/startup.js | 62 ++++++++++++++----------------- lib/ace/edit_session.js | 14 ++++--- lib/ace/mode/javascript.js | 34 +++++++++++++++++ lib/ace/mode/javascript_worker.js | 24 ++++++++++++ lib/ace/mode/text.js | 4 ++ lib/ace/worker/Demo.js | 16 -------- lib/ace/worker/mirror.js | 17 ++++++--- lib/ace/worker/worker_client.js | 4 ++ 8 files changed, 114 insertions(+), 61 deletions(-) create mode 100644 lib/ace/mode/javascript_worker.js delete mode 100644 lib/ace/worker/Demo.js diff --git a/demo/startup.js b/demo/startup.js index 5778e6a0..1403fd1b 100644 --- a/demo/startup.js +++ b/demo/startup.js @@ -59,48 +59,42 @@ exports.launch = function(env) { var emacs = require("ace/keyboard/keybinding/emacs").Emacs; var HashHandler = require("ace/keyboard/hash_handler").HashHandler; - var WorkerClient = require("ace/worker/worker_client").WorkerClient; - var docs = {}; docs.js = new EditSession(document.getElementById("jstext").innerHTML); docs.js.setMode(new JavaScriptMode()); docs.js.setUndoManager(new UndoManager()); - 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); - }); + if (false && window.Worker) { + var worker = new WorkerClient("../..", ["ace", "pilot"], "ace/worker/mirror", "Mirror"); + worker.call("setValue", [docs.js.getValue()]); - worker.on("jslint", function(results) { - var errors = []; - for (var i=0; i Date: Sun, 23 Jan 2011 15:02:38 +0100 Subject: [PATCH 12/29] worker cleanup --- lib/ace/mode/javascript.js | 4 ++++ lib/ace/worker/worker_client.js | 1 + 2 files changed, 5 insertions(+) diff --git a/lib/ace/mode/javascript.js b/lib/ace/mode/javascript.js index 3d4d62d2..f18ec329 100644 --- a/lib/ace/mode/javascript.js +++ b/lib/ace/mode/javascript.js @@ -152,6 +152,10 @@ oop.inherits(Mode, TextMode); session.setAnnotations(errors) }); + worker.on("terminate", function() { + session.clearAnnotations(); + }); + return worker; }; diff --git a/lib/ace/worker/worker_client.js b/lib/ace/worker/worker_client.js index 81a516a7..71d6505e 100644 --- a/lib/ace/worker/worker_client.js +++ b/lib/ace/worker/worker_client.js @@ -65,6 +65,7 @@ var WorkerClient = function(baseUrl, topLevelNamespaces, module, classname) { oop.implement(this, EventEmitter); this.terminate = function() { + this._dispatchEvent("terminate", {}); this.$worker.terminate(); }; From 6097f1cdfc73f2b8f19c5d2f72b70c0748e0104b Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Sun, 23 Jan 2011 17:47:31 +0100 Subject: [PATCH 13/29] add some eclipse icons --- demo/icons/Readme.txt | 1 + demo/icons/epl.html | 260 +++++++++++++++++++++++++++++++++++++ demo/icons/error_obj.gif | Bin 0 -> 339 bytes demo/icons/warning_obj.gif | Bin 0 -> 324 bytes demo/styles.css | 62 +++++++++ 5 files changed, 323 insertions(+) create mode 100644 demo/icons/Readme.txt create mode 100644 demo/icons/epl.html create mode 100644 demo/icons/error_obj.gif create mode 100644 demo/icons/warning_obj.gif create mode 100644 demo/styles.css 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 0000000000000000000000000000000000000000..0bc60689c6dcbfb71770789a3c2e98f51f6eecd7 GIT binary patch literal 339 zcmZ?wbhEHb6krfwxXQrrHNWU4 z&?^&f5)>{J;LlXy?-r(U7vw8pD9sGYH>i^m7 z{}+n?Uupb*wdw!W=Kpu5|Gzi)|MMOH&*W4G3O9s`Hie5dhKhAYOSDHz^u$R`Ns&F; ztb4Lu|5%IOu~xma-9{(d4NrF&pY1liGsFJ=Y^MivoF6U?J>712zT5O-ui5SC4)jB<~Eh3Ck(|MR1tY+CYw%7@BZ0uHG65^MT;^$&kcave^ j77&vV;AWL^lhtKopEXNY&Ru%J!UdsAmM(L0WUvMR>~MP$ literal 0 HcmV?d00001 diff --git a/demo/styles.css b/demo/styles.css new file mode 100644 index 00000000..44ab6810 --- /dev/null +++ b/demo/styles.css @@ -0,0 +1,62 @@ + html { + height: 100%; + overflow: hidden; +} + +body { + overflow: hidden; + margin: 0; + padding: 0; + height: 100%; + width: 100%; + font-family: Arial, Helvetica, sans-serif, Tahoma, Verdana, sans-serif; + font-size: 12px; + background: rgb(14, 98, 165); + color: white; +} + +#editor { + top: 55px; + left: 0px; + background: white; +} + +.ace_gutter-cell.ace_error { + background-image: url("icons/error_obj.gif"); + background-repeat: no-repeat; + background-position: 4px center; +} + +#controls { + width: 100%; + height: 55px; +} + +#jump { + position: absolute; + width: 10px; + height: 10px; + border: 1px solid red; + z-index: 10000; + display: none; +} + +#cockpitInput { + position: absolute; + width: 100%; + bottom: 0; + + border: none; outline: none; + font-family: consolas, courier, monospace; + font-size: 120%; +} + +#cockpitOutput { + padding: 10px; + margin: 0 15px; + border: 1px solid #AAA; + -moz-border-radius-topleft: 10px; + -moz-border-radius-topright: 10px; + border-top-left-radius: 4px; border-top-right-radius: 4px; + background: #DDD; color: #000; +} \ No newline at end of file From 7d919c0516600017d0ff51d2e1c63079c1d2826c Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Sun, 23 Jan 2011 18:29:57 +0100 Subject: [PATCH 14/29] this timeout does more harm than it helps. Prevents iOS keyboard from opening and has problems in FF4 --- lib/ace/editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 6aeeee4b..26fbb1c2 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) { From 40627c28250363ec3aa482b5f0e905bb6cac7bc8 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 25 Jan 2011 13:32:42 +0100 Subject: [PATCH 15/29] add warning style --- demo/styles.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/demo/styles.css b/demo/styles.css index 44ab6810..ff6a749f 100644 --- a/demo/styles.css +++ b/demo/styles.css @@ -27,6 +27,12 @@ body { background-position: 4px center; } +.ace_gutter-cell.ace_warning { + background-image: url("icons/warning_obj.gif"); + background-repeat: no-repeat; + background-position: 4px center; +} + #controls { width: 100%; height: 55px; From ef90df695a3a6fad3d61bbfc4f1a351d3f5ccd43 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 25 Jan 2011 13:32:58 +0100 Subject: [PATCH 16/29] remove "mac" whitespace --- lib/ace/edit_session.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 701cb543..1177e245 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -531,9 +531,9 @@ var EditSession = function(text, mode) { var lastDelta = deltas[deltas.length-1]; this.selection.clearSelection(); - if (firstDelta.action == "insertText" || firstDelta.action == "insertLines") + if (firstDelta.action == "insertText" || firstDelta.action == "insertLines") this.selection.moveCursorToPosition(firstDelta.range.start); - if (firstDelta.action == "removeText" || firstDelta.action == "removeLines") + if (firstDelta.action == "removeText" || firstDelta.action == "removeLines") this.selection.setSelectionRange(Range.fromPoints(firstDelta.range.start, lastDelta.range.end)); }, @@ -550,9 +550,9 @@ var EditSession = function(text, mode) { var lastDelta = deltas[deltas.length-1]; this.selection.clearSelection(); - if (firstDelta.action == "insertText" || firstDelta.action == "insertLines") + if (firstDelta.action == "insertText" || firstDelta.action == "insertLines") this.selection.setSelectionRange(Range.fromPoints(firstDelta.range.start, lastDelta.range.end)); - if (firstDelta.action == "removeText" || firstDelta.action == "removeLines") + if (firstDelta.action == "removeText" || firstDelta.action == "removeLines") this.selection.moveCursorToPosition(firstDelta.range.start); }, From f9d03eed7b97d98f240cf12b6f9ca6a290c75d23 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 25 Jan 2011 13:33:37 +0100 Subject: [PATCH 17/29] requireJS version of narcissus --- lib/ace/narcissus/jsdefs.js | 375 +++++++++ lib/ace/narcissus/jslex.js | 460 +++++++++++ lib/ace/narcissus/jsparse.js | 1432 ++++++++++++++++++++++++++++++++++ 3 files changed, 2267 insertions(+) create mode 100644 lib/ace/narcissus/jsdefs.js create mode 100644 lib/ace/narcissus/jslex.js create mode 100644 lib/ace/narcissus/jsparse.js diff --git a/lib/ace/narcissus/jsdefs.js b/lib/ace/narcissus/jsdefs.js new file mode 100644 index 00000000..09215364 --- /dev/null +++ b/lib/ace/narcissus/jsdefs.js @@ -0,0 +1,375 @@ +/* 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. + * + * 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) { + // diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 1177e245..3d768a58 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -241,7 +241,7 @@ var EditSession = function(text, mode) { }; this.tokenRe = /^[\w\d]+/g; - this.nonTokenRe = /^[^\w\d]+/g; + this.nonTokenRe = /^[^\w\d|\x00-\xff]+/g; this.getWordRange = function(row, column) { var line = this.getLine(row); @@ -633,19 +633,23 @@ var EditSession = function(text, mode) { var screenColumn = 0; var remaining = docColumn; - - var line = this.getLine(row).split("\t"); - 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; + if(c == '\t'){ + screenColumn += tabSize; + } else if(/[^\x00-\xff]/.exec(c)){ + screenColumn += 2; + } else { + screenColumn += 1; + } + }else{ + break; + } + } return screenColumn; }; @@ -655,23 +659,24 @@ 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; + if(c == '\t'){ + remaining -= tabSize; + } else if(/[^\x00-\xff]/.exec(c)){ + remaining -= 2; + } else { + remaining -= 1; + } + }else{ + break; + } + } + return docColumn; }; diff --git a/lib/ace/layer/text.js b/lib/ace/layer/text.js index ecaa6698..815f669a 100644 --- a/lib/ace/layer/text.js +++ b/lib/ace/layer/text.js @@ -274,6 +274,17 @@ var Text = function(parentEl) { .replace(/" + w + ""; + }else{ + buffer = buffer + w; + } + } + output = buffer; if (!this.$textToken[token.type]) { var classes = "ace_" + token.type.replace(/\./g, " ace_"); diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index affce7fb..0ae38f7d 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -591,12 +591,35 @@ var VirtualRenderer = function(container, theme) { }; this.showComposition = function(position) { + var cur = this.$cursorLayer.element.firstChild; + var scroll = this.scroller; + var top = (parseInt(cur.style.top) || 0) + (parseInt(scroll.style.top) || 0); + var left = (parseInt(cur.style.left) || 0) + (parseInt(scroll.style.left) || 0); + + var edit = document.getElementById("editor").lastChild; + edit.style.color = '#66ff33'; + edit.style.top = (top - 1) + "px"; + edit.style.left = (left + 3) + "px"; + edit.style.background = "transparent"; + edit.style.border = "none"; + edit.style.resize = "none"; + edit.style.outline = "none"; + edit.style.width = "10000px"; + edit.style.height = "10000px"; + edit.style.fontSize = "12px"; + + this.hideCursor(); }; this.setCompositionText = function(text) { }; this.hideComposition = function() { + this.showCursor(); + var edit = document.getElementById("editor").lastChild; + edit.className = ''; + edit.style.top = "-10000px"; + edit.style.left = "-10000px"; }; this.setTheme = function(theme) { diff --git a/static.py b/static.py index 8cac3b7a..801a70d7 100755 --- a/static.py +++ b/static.py @@ -231,7 +231,7 @@ def test(): app = Cling(getcwd()) try: print "Serving " + getcwd() + " to http://localhost:9999" - make_server('localhost', 9999, validator(app)).serve_forever() + make_server('0.0.0.0', 9999, validator(app)).serve_forever() except KeyboardInterrupt, ki: print "" print "Ciao, baby!" From 681dace8ce33e6b0177ce850b1540315097afd61 Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Tue, 25 Jan 2011 23:00:01 +0800 Subject: [PATCH 22/29] Removing unnecessary and dependency on heavy 'node-o3-xml'. --- .gitmodules | 3 --- support/node-o3-xml | 1 - 2 files changed, 4 deletions(-) delete mode 160000 support/node-o3-xml 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/support/node-o3-xml b/support/node-o3-xml deleted file mode 160000 index 563a2b10..00000000 --- a/support/node-o3-xml +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 563a2b1091bd1b9a7d26a9f597aacb4341edb619 From 383e493fa0f450f5c097a0f03d9d426aa78ee4f6 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 25 Jan 2011 21:36:51 +0100 Subject: [PATCH 23/29] fire event when session changes --- lib/ace/editor.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 26fbb1c2..efa1cb9a 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -122,6 +122,7 @@ 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); @@ -172,6 +173,11 @@ var Editor =function(renderer, session) { this.onDocumentChangeAnnotation(); this.renderer.scrollToRow(session.getScrollTopRow()); this.renderer.updateFull(); + + this._dispatchEvent("changeSession", { + session: session, + oldSession: oldSession + }); }; this.getSession = function() { From 56c97c992c544111b69684133076f49b978d10c6 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 25 Jan 2011 21:37:19 +0100 Subject: [PATCH 24/29] move hidden text area over the cursor --- lib/ace/keyboard/textinput.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) 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(); From 149c7c3ff55b5ff41109358f393ec14f020842ca Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 25 Jan 2011 21:37:56 +0100 Subject: [PATCH 25/29] finish cjk support --- lib/ace/css/editor.css | 20 ++++++++++++ lib/ace/edit_session.js | 64 ++++++++++++++++++++++++++----------- lib/ace/layer/text.js | 18 ++++------- lib/ace/virtual_renderer.js | 48 +++++++++++++++------------- 4 files changed, 97 insertions(+), 53 deletions(-) diff --git a/lib/ace/css/editor.css b/lib/ace/css/editor.css index cd537ddd..0390dc5a 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; @@ -51,8 +65,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 3d768a58..5ae0998c 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -241,8 +241,7 @@ var EditSession = function(text, mode) { }; this.tokenRe = /^[\w\d]+/g; - this.nonTokenRe = /^[^\w\d|\x00-\xff]+/g; - + this.nonTokenRe = /^(?:[^\w\d|[\u3040-\u309F]|[\u30A0-\u30FF]|[\u4E00-\u9FFF\uF900-\uFAFF\u3400-\u4DBF])+/g this.getWordRange = function(row, column) { var line = this.getLine(row); @@ -635,18 +634,27 @@ var EditSession = function(text, mode) { var remaining = docColumn; var line = this.getLine(row); - for(var i=0; i 0){ + for (var i=0; i 0) { remaining -= 1; - if(c == '\t'){ + // tab + if (c == 9) { screenColumn += tabSize; - } else if(/[^\x00-\xff]/.exec(c)){ - screenColumn += 2; + } + // 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{ + } else { break; } } @@ -661,22 +669,42 @@ var EditSession = function(text, mode) { var remaining = screenColumn; var line = this.getLine(row); - for(var i=0; i 0){ + for(var i=0; i 0) { docColumn += 1; - if(c == '\t'){ - remaining -= tabSize; - } else if(/[^\x00-\xff]/.exec(c)){ - remaining -= 2; + // 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{ + } else { break; } } - + return docColumn; }; diff --git a/lib/ace/layer/text.js b/lib/ace/layer/text.js index 815f669a..568f1db9 100644 --- a/lib/ace/layer/text.js +++ b/lib/ace/layer/text.js @@ -266,6 +266,8 @@ var Text = function(parentEl) { var spaceReplace = " "; } + var characterWidth = this.config.characterWidth; + for ( var i = 0; i < tokens.length; i++) { var token = tokens[i]; @@ -273,19 +275,11 @@ var Text = function(parentEl) { .replace(/&/g, "&") .replace(/" + c + "" + }); - var buffer = ''; - for (var j=0;j" + w + ""; - }else{ - buffer = buffer + w; - } - } - output = buffer; - if (!this.$textToken[token.type]) { var classes = "ace_" + token.type.replace(/\./g, " ace_"); stringBuilder.push("", output, ""); diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index 0ae38f7d..fba0aca1 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -67,8 +67,7 @@ var VirtualRenderer = function(container, theme) { this.container.appendChild(this.scroller); this.content = document.createElement("div"); - this.content.style.cssText = "position:absolute;box-sizing:border-box;" + - "-moz-box-sizing:border-box;-webkit-box-sizing:border-box"; + this.content.className = "ace_content"; this.scroller.appendChild(this.content); this.$gutterLayer = new GutterLayer(this.$gutter); @@ -591,35 +590,38 @@ var VirtualRenderer = function(container, theme) { }; this.showComposition = function(position) { - var cur = this.$cursorLayer.element.firstChild; - var scroll = this.scroller; - var top = (parseInt(cur.style.top) || 0) + (parseInt(scroll.style.top) || 0); - var left = (parseInt(cur.style.left) || 0) + (parseInt(scroll.style.left) || 0); - - var edit = document.getElementById("editor").lastChild; - edit.style.color = '#66ff33'; - edit.style.top = (top - 1) + "px"; - edit.style.left = (left + 3) + "px"; - edit.style.background = "transparent"; - edit.style.border = "none"; - edit.style.resize = "none"; - edit.style.outline = "none"; - edit.style.width = "10000px"; - edit.style.height = "10000px"; - edit.style.fontSize = "12px"; - + console.log("show composition") + if (!this.$composition) { + this.$composition = document.createElement("div"); + this.$composition.className = "ace_composition"; + this.content.appendChild(this.$composition); + } + + this.$composition.innerHTML = " "; + + var pos = this.$cursorLayer.getPixelPosition(); + var style = this.$composition.style; + style.top = pos.top + "px"; + style.left = (pos.left + this.$padding) + "px"; + style.height = this.lineHeight + "px"; + //style.width = this.characterWidth + "px"; + this.hideCursor(); }; this.setCompositionText = function(text) { + this.$composition.innerText = this.$composition.textContent = text; }; this.hideComposition = function() { this.showCursor(); - var edit = document.getElementById("editor").lastChild; - edit.className = ''; - edit.style.top = "-10000px"; - edit.style.left = "-10000px"; + + if (!this.$composition) + return; + + var style = this.$composition.style; + style.top = "-10000px"; + style.left = "-10000px"; }; this.setTheme = function(theme) { From 34278a590a497a43de9312003383bbf1f203b07a Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 25 Jan 2011 21:38:13 +0100 Subject: [PATCH 26/29] add missing function to mock --- lib/ace/test/mockrenderer.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/ace/test/mockrenderer.js b/lib/ace/test/mockrenderer.js index be4cd520..412a418a 100644 --- a/lib/ace/test/mockrenderer.js +++ b/lib/ace/test/mockrenderer.js @@ -129,5 +129,8 @@ MockRenderer.prototype.showCursor = function() { MockRenderer.prototype.visualizeFocus = function() { }; +MockRenderer.prototype.setAnnotations = function() { +}; + return MockRenderer; }); From 2844d1d08dc6bc92680b9a1bcaff706fd261abcb Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 25 Jan 2011 21:38:35 +0100 Subject: [PATCH 27/29] add box model property to the CSS mode --- lib/ace/mode/css_highlight_rules.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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|" + From c388ba2964cd3b971002b799263272c3b118db83 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 25 Jan 2011 21:38:47 +0100 Subject: [PATCH 28/29] remove outdated build demo --- editor-build.html | 213 ---------------------------------------------- 1 file changed, 213 deletions(-) delete mode 100644 editor-build.html diff --git a/editor-build.html b/editor-build.html deleted file mode 100644 index b506decc..00000000 --- a/editor-build.html +++ /dev/null @@ -1,213 +0,0 @@ - - - - - - Editor - - - - - -
- - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - -
- info -
-
- -
-
- - - - - - - - - - - - - - - - - - - From fb1cedcbc298fd763a9204afe76bd385e7bad8a5 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Tue, 25 Jan 2011 21:40:00 +0100 Subject: [PATCH 29/29] update pilot --- support/pilot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/support/pilot b/support/pilot index d1efe31c..af903446 160000 --- a/support/pilot +++ b/support/pilot @@ -1 +1 @@ -Subproject commit d1efe31c7df29526e6ffd8fe294dbd54b8227e61 +Subproject commit af90344687c8486892b44d5decc4c2181df3a6cf