From 6915188a461b455e7112634f7a72612a9c6de54d Mon Sep 17 00:00:00 2001 From: RayMorgan Date: Thu, 24 Sep 2009 18:56:37 -0700 Subject: [PATCH] Enhanced the repl library. Now supports: - command options: .help, .break, .clear, .exit - local vars and global functions - ability to print 0, false and "" - when value is a function, prints [Function] - when object is circular, prints [Circular Object] instead of throwing an error --- lib/repl.js | 174 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 156 insertions(+), 18 deletions(-) diff --git a/lib/repl.js b/lib/repl.js index 46b83b50b4..a8d8f2686c 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -1,37 +1,175 @@ // A repl library that you can include in your own code to get a runtime // interface to your program. Just require("/repl.js"). +puts("Type '.help' for options."); + node.stdio.open(); +node.stdio.addListener("data", readline); var buffered_cmd = ''; var trimmer = /^\s*(.+)\s*$/m; +var scopedVar = /^\s*var\s*([_\w\$]+)(.*)$/m; +var scopeFunc = /^\s*function\s*([_\w\$]+)/; exports.prompt = "node> "; - -function displayPrompt () { - print(buffered_cmd.length ? '... ' : exports.prompt); -} +exports.scope = {}; displayPrompt(); -node.stdio.addListener("data", function (cmd) { - var matches = trimmer.exec(cmd); - if (matches && matches.length == 2) { - cmd = matches[1]; +/** + * The main REPL function. This is called everytime the user enters + * data on the command line. + */ +function readline (cmd) { + cmd = trimWhitespace(cmd); + + // Check to see if a REPL keyword was used. If it returns true, + // display next prompt and return. + if (parseREPLKeyword(cmd) === true) { + return; + } + + // The catchall for errors + try { + buffered_cmd += cmd; + // This try is for determining if the command is complete, or should + // continue onto the next line. try { - buffered_cmd += cmd; - try { - puts(JSON.stringify(eval(buffered_cmd))); - buffered_cmd = ''; - } catch (e) { - if (!(e instanceof SyntaxError)) - throw e; + buffered_cmd = convertToScope(buffered_cmd); + + // Scope the readline with exports.scope to provide "local" vars + with (exports.scope) { + var ret = eval(buffered_cmd); + printValue(ret); } - } catch (e) { - puts('caught an exception: ' + e); + buffered_cmd = ''; + } catch (e) { + if (!(e instanceof SyntaxError)) + throw e; } + } catch (e) { + // On error: Print the error and clear the buffer + puts('caught an exception: ' + e); + buffered_cmd = ''; } + displayPrompt(); -}); +} + + +/** + * Used to display the prompt. + */ +function displayPrompt () { + print(buffered_cmd.length ? '... ' : exports.prompt); +} + +/** + * Used to parse and execute the Node REPL commands. + * + * @param {cmd} cmd The command entered to check + * @returns {Boolean} If true it means don't continue parsing the command + */ +function parseREPLKeyword (cmd) { + switch (cmd) { + case ".break": + buffered_cmd = ''; + displayPrompt(); + return true; + case ".clear": + puts("Clearing Scope..."); + buffered_cmd = ''; + exports.scope = {}; + displayPrompt(); + return true; + case ".exit": + node.stdio.close(); + return true; + case ".help": + puts(".break\tSometimes you get stuck in a place you can't get out... This will get you out."); + puts(".clear\tBreak, and also clear the local scope."); + puts(".exit\tExit the prompt"); + puts(".help\tShow repl options"); + displayPrompt(); + return true; + } + return 0; +} + +/** + * Trims Whitespace from a line. + * + * @param {String} cmd The string to trim the whitespace from + * @returns {String} The trimmed string + */ +function trimWhitespace (cmd) { + var matches = trimmer.exec(cmd); + if (matches && matches.length == 2) { + return matches[1]; + } +} + +/** + * Converts commands that use var and function () to use the + * local exports.scope when evaled. This provides a local scope + * on the REPL. + * + * @param {String} cmd The cmd to convert + * @returns {String} The converted command + */ +function convertToScope (cmd) { + var matches; + + // Replaces: var foo = "bar"; with: exports.scope.foo = bar; + matches = scopedVar.exec(cmd); + if (matches && matches.length == 3) { + return "exports.scope." + matches[1] + matches[2]; + } + + // Replaces: function foo() {}; with: foo = function foo() {}; + matches = scopeFunc.exec(buffered_cmd); + if (matches && matches.length == 2) { + return matches[1] + " = " + buffered_cmd; + } + + return cmd; +} + +/** + * Echos the value of a value. Trys to print the value out + * in the best way possible given the different types. + * + * @param {Object} value The object to print out + */ +function printValue (value) { + if (value === 0) { + puts("0"); + return; + } + + if (value === false) { + puts("false"); + return; + } + + if (value === "") { + puts('""'); + return; + } + + if (typeof(value) == "function") { + puts("[Function]"); + return; + } + + try { + puts(JSON.stringify(value)); + } catch (e) { + if (e.message.search("circular")) + puts("[Circular Object]"); + else + throw e; + } +}