|
| 1 | +(function(global) { |
| 2 | +"use strict"; |
| 3 | + |
| 4 | +// --- dependency modules ---------------------------------- |
| 5 | +var CoffeeScript = global["CoffeeScript"]; |
| 6 | + |
| 7 | +// --- class / interfaces ---------------------------------- |
| 8 | +function CoffeeScriptREPL(){ |
| 9 | + this["log"] = ""; |
| 10 | + this["buffer"] = "coffee> "; |
| 11 | + this["defaultInput"] = ""; |
| 12 | + this["inputQueue"] = []; |
| 13 | + this["outputQueue"] = []; |
| 14 | +} |
| 15 | + |
| 16 | +CoffeeScriptREPL["help"] = (function(){/* |
| 17 | +.help show repl options |
| 18 | +.[n] revert last nth input |
| 19 | +.hist view input history |
| 20 | +.clear clear log |
| 21 | +
|
| 22 | +c[space][enter] |
| 23 | +cons[space][enter] |
| 24 | +console.[space][enter] |
| 25 | +console.lo[space][enter] |
| 26 | + autosuggestion |
| 27 | +
|
| 28 | +log(str) alt console.log() |
| 29 | +clear() alt console.clear() |
| 30 | +dir(obj [, depth]) |
| 31 | + alt console.dir() |
| 32 | +type(obj) alt typeof() |
| 33 | +load(url) load js file |
| 34 | +$$[n] last nth result variable |
| 35 | +*/}).toString()//.match(/^function\s*\(\s*\)\s*\{\s*\/\*([\S\s]*?)\*\/\s*\}$/); |
| 36 | + |
| 37 | +CoffeeScriptREPL["prototype"] = { |
| 38 | + "terminal": CoffeeScriptREPL_terminal, // Function(String):void |
| 39 | + "evaluate": CoffeeScriptREPL_evaluate, // Function(String):void |
| 40 | + "addLog": CoffeeScriptREPL_addLog, // Function(String):void |
| 41 | + "clearLog": CoffeeScriptREPL_clearLog // Function():void |
| 42 | +}; |
| 43 | + |
| 44 | +// --- implements ------------------------------------------ |
| 45 | +function CoffeeScriptREPL_terminal(input){ // @arg String |
| 46 | + // @ret Void |
| 47 | + var n, that = this; |
| 48 | + this["defaultInput"] = ""; |
| 49 | + if (n = (/^\.([123456789](?:\d+)?)/.exec(input) || ["", ""])[1]) { |
| 50 | + if(!!this["inputQueue"][this["inputQueue"]["length"] - n]){ |
| 51 | + this["defaultInput"] = this["inputQueue"][this["inputQueue"]["length"] - n]; |
| 52 | + } else { |
| 53 | + this["defaultInput"] = input; |
| 54 | + } |
| 55 | + } else if (/^\.hi/.test(input)) { |
| 56 | + this["buffer"] = "coffee> " + input + "\n" + this["inputQueue"]["map"](function(str, i){ |
| 57 | + return that["inputQueue"]["length"] - i + ": " + str |
| 58 | + })["join"]("\n") |
| 59 | + } else if (/\s$/.test(input)) { |
| 60 | + var tmp = autocomplete(input["trimRight"]()); |
| 61 | + if(tmp["results"]["length"] === 0){ |
| 62 | + this["defaultInput"] = input["trimRight"](); |
| 63 | + } else if(tmp["results"]["length"] === 1){ |
| 64 | + this["defaultInput"] = tmp["tokens"][0] + tmp["tokens"][1] + (tmp["tokens"][1]["length"] === 0 ? "" : ".") + tmp["results"][0]; |
| 65 | + } else { |
| 66 | + this["buffer"] = "coffee> " + input + "\n" + tmp["results"]["join"](" \t"); |
| 67 | + this["defaultInput"] = input["trimRight"](); |
| 68 | + } |
| 69 | + } else if (/^.c/.test(input)) { |
| 70 | + this["clearLog"](); |
| 71 | + } else if (/^.he/.test(input)) { |
| 72 | + this["buffer"] = "coffee> " + input + "\n" + CoffeeScriptREPL["help"]; |
| 73 | + } else { |
| 74 | + this["buffer"] = ""; |
| 75 | + this["log"] += "coffee> " + input + "\n"; |
| 76 | + try{ |
| 77 | + var _log = dump(this["evaluate"](input), 0) + "\n"; |
| 78 | + }catch(err){ |
| 79 | + var _log = err["stack"] + "\n"; |
| 80 | + } |
| 81 | + this["log"] += _log; |
| 82 | + } |
| 83 | +} |
| 84 | + |
| 85 | +function CoffeeScriptREPL_evaluate(input){ // @arg String |
| 86 | + // @ret Void |
| 87 | + this["inputQueue"]["push"](input); |
| 88 | + var that = this; |
| 89 | + var preprocessed = "do ->\n " + input["split"]("\n")["join"]("\n "); |
| 90 | + var expanded = macroexpand(preprocessed); |
| 91 | + var env = { |
| 92 | + "log": function(o){ that["addLog"](o); }, |
| 93 | + "clear": function(){ that["clearLog"](); }, |
| 94 | + "dir": function(o, i){ return dump(o, i); }, |
| 95 | + "type": function(o){ return type(o); }, |
| 96 | + "load": function(url){ load(url); } |
| 97 | + }; |
| 98 | + this["outputQueue"]["forEach"](function(val, i){ |
| 99 | + env["$$"+(that["outputQueue"]["length"] - 1 - i)] = val; |
| 100 | + }); |
| 101 | + var output = evaluate(expanded, env); |
| 102 | + this["outputQueue"]["push"](output); |
| 103 | + return output; |
| 104 | +} |
| 105 | + |
| 106 | +function CoffeeScriptREPL_addLog(o){ |
| 107 | + this["log"] += dump(o) + "\n"; |
| 108 | +} |
| 109 | + |
| 110 | +function CoffeeScriptREPL_clearLog(){ |
| 111 | + this["log"] = ""; |
| 112 | + this["buffer"] = "coffee> "; |
| 113 | + this["defaultInput"] = ""; |
| 114 | +} |
| 115 | + |
| 116 | +// --- functions ------------------------------------------ |
| 117 | +function load(url, // @arg URLString |
| 118 | + callback) { // @arg Function |
| 119 | + // @ret Void |
| 120 | + var script = document.createElement("script"); |
| 121 | + script.src = url; |
| 122 | + script.onload = callback; |
| 123 | + document.body.appendChild(script); |
| 124 | +} |
| 125 | + |
| 126 | +function macroexpand(code) { // @arg CoffeeScriptString |
| 127 | + // @ret JavaScriptString |
| 128 | + var compiled = CoffeeScript["compile"](code, {"bare": true}); |
| 129 | + var replaced = compiled["replace"](/var[^\;]+;\n\n/, ""); |
| 130 | + return replaced; |
| 131 | +} |
| 132 | + |
| 133 | +function evaluate(code, // @arg String |
| 134 | + env) { // @arg Object |
| 135 | + // @ret Any |
| 136 | + env = env || {}; |
| 137 | + var vars = Object.keys(env); |
| 138 | + var vals = vars.map(function(key){ return env[key]; }); |
| 139 | + var args = vars.concat("return " + code + ";"); |
| 140 | + var fn = Function.apply(this, args); |
| 141 | + return fn.apply(self, vals); |
| 142 | +} |
| 143 | +console.assert(evaluate("unko", {unko:0}) === 0, "evaluate"); |
| 144 | + |
| 145 | +function getPropertys(o){ // @arg Object |
| 146 | + // @ret StringArray |
| 147 | + /* |
| 148 | + var keys1 = (function(o){ |
| 149 | + var results = []; |
| 150 | + for(key in o) results.push(key); |
| 151 | + return results; |
| 152 | + })(o); |
| 153 | + var keys2 = Object.keys(o); |
| 154 | + */ |
| 155 | + var keys3 = (function(o){ |
| 156 | + var results = []; |
| 157 | + while(o) { |
| 158 | + results = results.concat(Object.getOwnPropertyNames(o)); |
| 159 | + o = Object.getPrototypeOf(o); |
| 160 | + } |
| 161 | + return results; |
| 162 | + })(o); |
| 163 | + //var keys = [].concat(keys1, keys2, keys3); |
| 164 | + var keys = keys3; |
| 165 | + var merged = keys.reduce(function(_, key){ |
| 166 | + if(!!_["keySet"][key]) return _; |
| 167 | + _["keySet"][key] = true; |
| 168 | + _["results"]["push"](key); |
| 169 | + return _; |
| 170 | + },{"keySet":{}, "results":[]})["results"]; |
| 171 | + var sorted = merged.sort(); |
| 172 | + return sorted; |
| 173 | +} |
| 174 | +console.assert(getPropertys({a:0, b:0}).length === 2, "getPropertys"); |
| 175 | + |
| 176 | +function suggest(env, // @arg Object |
| 177 | + keyword){ // @arg String |
| 178 | + // @ret StringArray |
| 179 | + var reg = new RegExp("^" + keyword + ".*"); |
| 180 | + var keys = getPropertys(env); |
| 181 | + var candidates = keys.filter(function(key) { |
| 182 | + return reg.test(key) && key !== keyword; |
| 183 | + }); |
| 184 | + return candidates; |
| 185 | +} |
| 186 | +console.assert(suggest(self, "sel").length === 1, "suggest"); |
| 187 | + |
| 188 | +function autocomplete(code) { // @arg String |
| 189 | + // @ret Object |
| 190 | + var reg = /((?:[A-Za-z0-9$_](?:\.(?:[A-Za-z0-9$_]+)?)?)+)$/; |
| 191 | + var exp = (reg.exec(code) || ["", ""])[1] |
| 192 | + var pre = code.replace(exp, ""); |
| 193 | + var arr = exp.split("."); |
| 194 | + var env = arr.slice(0, arr.length-1).join("."); |
| 195 | + var key = arr.slice(arr.length-1)[0]; |
| 196 | + if(key.length === 0){ |
| 197 | + var results = getPropertys(evaluate(env)); |
| 198 | + } else if (env.length === 0){ |
| 199 | + var results = suggest(self, key); |
| 200 | + } else { |
| 201 | + var results = suggest(evaluate(env), key); |
| 202 | + } |
| 203 | + if(exp === key){ |
| 204 | + return {"tokens": [pre, "", key], "results": results}; |
| 205 | + } else{ |
| 206 | + return {"tokens": [pre, env, key], "results": results}; |
| 207 | + } |
| 208 | +} |
| 209 | +console.assert(autocomplete("if" ).results.length === 0, "autocomplete 1"); |
| 210 | +console.assert(autocomplete("if win").results[0] === "window", "autocomplete 2"); |
| 211 | +console.assert(autocomplete("if window.sel").results[0] === "self", "autocomplete 2"); |
| 212 | + |
| 213 | +function type(o) { // @arg Object |
| 214 | + // @ret String |
| 215 | + if (o === null) { return "null"; |
| 216 | + } else if (o === void 0) { return "undefined"; |
| 217 | + } else if (o === self) { return "global"; |
| 218 | + } else if (o["nodeType"] != null) { return "node"; |
| 219 | + } else if (typeof o !== "object") { return typeof o; |
| 220 | + } else { |
| 221 | + var str = Object.prototype.toString.call(o); |
| 222 | + return ((str === "[object Object]" ? |
| 223 | + /^\s*function\s+(\w+)/.exec("" + o["constructor"]) : |
| 224 | + /^\[object (\w+)\]$/.exec(str) |
| 225 | + ) || ["", "object"])[1]["toLowerCase"](); |
| 226 | + } |
| 227 | +} |
| 228 | +console.assert(type(null) === "null", "type null"); |
| 229 | +console.assert(type(void 0) === "undefined", "type undefined"); |
| 230 | +console.assert(type(true) === "boolean", "type boolean"); |
| 231 | +console.assert(type(0) === "number", "type number"); |
| 232 | +console.assert(type("string") === "string", "type string"); |
| 233 | +console.assert(type(function() {}) === "function", "type function"); |
| 234 | +console.assert(type([]) === "array", "type array"); |
| 235 | +console.assert(type({}) === "object", "type object"); |
| 236 | +console.assert(type(new Date) === "date", "type date"); |
| 237 | +console.assert(type(Math) === "math", "type math"); |
| 238 | +console.assert(type(/0/) === "regexp", "type regexp"); |
| 239 | +console.assert(type(window) === "global", "type global"); |
| 240 | +console.assert(type(document.createElement("div")) === "node", "type node"); |
| 241 | +console.assert(type(new (function Foo(){})) === "foo", "type foo"); |
| 242 | + |
| 243 | + |
| 244 | +function dump(o, // @arg Object |
| 245 | + depth){ // @arg Number? |
| 246 | + // @ret String |
| 247 | + depth = depth || 0; |
| 248 | + return recur(o, depth, 0, []); |
| 249 | + function recur(o, depth, i, ancestors){ |
| 250 | + switch (type(o)) { |
| 251 | + case "null": |
| 252 | + case "undefined": |
| 253 | + case "boolean": |
| 254 | + case "number": return "" + o; |
| 255 | + case "string": return "\"" + o.split('\n').join('\\n') + "\""; |
| 256 | + case "function": return Object.prototype.toString.call(o); |
| 257 | + case "date": return JSON.stringify(o); |
| 258 | + case "array": |
| 259 | + if (ancestors.some(function(_o){ return _o === o;})) { |
| 260 | + return Object.prototype.toString.call(o) + "// Recursive Definition"; |
| 261 | + } else if (i >= depth) { |
| 262 | + return Object.prototype.toString.call(o); |
| 263 | + } else { |
| 264 | + var arr = o.map(function(val){ |
| 265 | + return recur(val, depth, i + 1, ancestors.concat(o)); |
| 266 | + }).join(", "); |
| 267 | + return "[" + arr + "]"; |
| 268 | + } |
| 269 | + default: |
| 270 | + if (ancestors.some(function(_o){ return _o === o;})) { |
| 271 | + return Object.prototype.toString.call(o) + " <- Recursive Definition"; |
| 272 | + } else if (i >= depth) { |
| 273 | + return Object.prototype.toString.call(o); |
| 274 | + } else { |
| 275 | + var keys = getPropertys(o); |
| 276 | + if (keys.length === 0) { |
| 277 | + return "{}"; |
| 278 | + } else { |
| 279 | + var props = keys.map(function(key) { |
| 280 | + var val = recur(o[key], depth, i + 1, ancestors.concat(o)); |
| 281 | + return "" + space(i+1) + key + ": " + val; |
| 282 | + }).join(",\n"); |
| 283 | + return "{\n" + props + "\n" + space(i) + "}"; |
| 284 | + } |
| 285 | + } |
| 286 | + } |
| 287 | + } |
| 288 | +} |
| 289 | +console.assert(dump({a:{b:{c:0,d:0}}}) === "[object Object]", "dump {a:{b:{c:0,d:0}}}"); |
| 290 | +console.assert(dump({a:{b:{c:0,d:0}}},3) === "{\n a: {\n b: {\n c: 0,\n d: 0\n }\n }\n}", "dump {a:{b:{c:0,d:0}}}, 3"); |
| 291 | + |
| 292 | + |
| 293 | +function space(i){ // @arg Number |
| 294 | + // @ret String |
| 295 | + var result = ""; |
| 296 | + while(i--) result += " "; |
| 297 | + return result; |
| 298 | +} |
| 299 | +console.assert(space(0) === "", "space 0"); |
| 300 | +console.assert(space(1) === " ", "space 1"); |
| 301 | +console.assert(space(2) === " ", "space 2"); |
| 302 | + |
| 303 | +// --- exports --------------------------------------------- |
| 304 | +global["CoffeeScriptREPL" in global ? "CoffeeScriptREPL_" : "CoffeeScriptREPL"] = CoffeeScriptREPL; |
| 305 | + |
| 306 | +})((this || 0).self || global); |
0 commit comments