Created
June 29, 2012 20:52
-
-
Save zeekay/3020573 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env coffee | |
# A very simple Read-Eval-Print-Loop. Compiles one line at a time to JavaScript | |
# and evaluates it. Good for simple tests, or poking around the **Node.js** API. | |
# Using it looks like this: | |
# | |
# coffee> console.log "#{num} bottles of beer" for num in [99..1] | |
# Start by opening up `stdin` and `stdout`. | |
stdin = process.openStdin() | |
stdout = process.stdout | |
# Require the **coffee-script** module to get access to the compiler. | |
CoffeeScript = require 'coffee-script' | |
readline = require 'readline' | |
{inspect} = require 'util' | |
{Script} = require 'vm' | |
Module = require 'module' | |
# REPL Setup | |
# Config | |
REPL_PROMPT = 'coffee> ' | |
REPL_PROMPT_MULTILINE = '------> ' | |
REPL_PROMPT_CONTINUATION = '......> ' | |
enableColours = no | |
unless process.platform is 'win32' | |
enableColours = not process.env.NODE_DISABLE_COLORS | |
# Log an error. | |
error = (err) -> | |
stdout.write (err.stack or err.toString()) + '\n' | |
## Autocompletion | |
# Regexes to match complete-able bits of text. | |
ACCESSOR = /\s*([\w\.]+)(?:\.(\w*))$/ | |
SIMPLEVAR = /(\w+)$/i | |
# Returns a list of completions, and the completed text. | |
autocomplete = (text) -> | |
completeAttribute(text) or completeVariable(text) or [[], text] | |
# Attempt to autocomplete a chained dotted attribute: `one.two.three`. | |
completeAttribute = (text) -> | |
if match = text.match ACCESSOR | |
[all, obj, prefix] = match | |
try obj = Script.runInThisContext obj | |
catch e | |
return | |
return unless obj? | |
obj = Object obj | |
candidates = Object.getOwnPropertyNames obj | |
while obj = Object.getPrototypeOf obj | |
for key in Object.getOwnPropertyNames obj when key not in candidates | |
candidates.push key | |
completions = getCompletions prefix, candidates | |
[completions, prefix] | |
# Attempt to autocomplete an in-scope free variable: `one`. | |
completeVariable = (text) -> | |
free = text.match(SIMPLEVAR)?[1] | |
free = "" if text is "" | |
if free? | |
vars = Script.runInThisContext 'Object.getOwnPropertyNames(Object(this))' | |
keywords = (r for r in CoffeeScript.RESERVED when r[..1] isnt '__') | |
candidates = vars | |
for key in keywords when key not in candidates | |
candidates.push key | |
completions = getCompletions free, candidates | |
[completions, free] | |
# Return elements of candidates for which `prefix` is a prefix. | |
getCompletions = (prefix, candidates) -> | |
el for el in candidates when 0 is el.indexOf prefix | |
# Make sure that uncaught exceptions don't kill the REPL. | |
process.on 'uncaughtException', error | |
# The current backlog of multi-line code. | |
backlog = '' | |
# The main REPL function. **run** is called every time a line of code is entered. | |
# Attempt to evaluate the command. If there's an exception, print it out instead | |
# of exiting. | |
run = (buffer) -> | |
# remove single-line comments | |
buffer = buffer.replace /(^|[\r\n]+)(\s*)##?(?:[^#\r\n][^\r\n]*|)($|[\r\n])/, "$1$2$3" | |
# remove trailing newlines | |
buffer = buffer.replace /[\r\n]+$/, "" | |
if multilineMode | |
backlog += "#{buffer}\n" | |
repl.setPrompt REPL_PROMPT_CONTINUATION | |
repl.prompt() | |
return | |
if !buffer.toString().trim() and !backlog | |
repl.prompt() | |
return | |
code = backlog += buffer | |
if code[code.length - 1] is '\\' | |
backlog = "#{backlog[...-1]}\n" | |
repl.setPrompt REPL_PROMPT_CONTINUATION | |
repl.prompt() | |
return | |
repl.setPrompt REPL_PROMPT | |
backlog = '' | |
try | |
_ = global._ | |
returnValue = CoffeeScript.eval "_=(#{code}\n)", { | |
filename: 'repl' | |
modulename: 'repl' | |
} | |
if returnValue is undefined | |
global._ = _ | |
repl.output.write "#{inspect returnValue, no, 2, enableColours}\n" | |
catch err | |
error err | |
repl.prompt() | |
if stdin.readable and stdin.isRaw | |
# handle piped input | |
pipedInput = '' | |
repl = | |
prompt: -> stdout.write @_prompt | |
setPrompt: (p) -> @_prompt = p | |
input: stdin | |
output: stdout | |
on: -> | |
stdin.on 'data', (chunk) -> | |
pipedInput += chunk | |
return unless /\n/.test pipedInput | |
lines = pipedInput.split "\n" | |
pipedInput = lines[lines.length - 1] | |
for line in lines[...-1] when line | |
stdout.write "#{line}\n" | |
run line | |
return | |
stdin.on 'end', -> | |
for line in pipedInput.trim().split "\n" when line | |
stdout.write "#{line}\n" | |
run line | |
stdout.write '\n' | |
process.exit 0 | |
else | |
# Create the REPL by listening to **stdin**. | |
if readline.createInterface.length < 3 | |
repl = readline.createInterface stdin, autocomplete | |
stdin.on 'data', (buffer) -> repl.write buffer | |
else | |
repl = readline.createInterface stdin, stdout, autocomplete | |
multilineMode = off | |
# Handle multi-line mode switch | |
repl.input.on 'keypress', (char, key) -> | |
# test for Ctrl-v | |
return unless key and key.ctrl and not key.meta and not key.shift and key.name is 'v' | |
cursorPos = repl.cursor | |
repl.output.cursorTo 0 | |
repl.output.clearLine 1 | |
multilineMode = not multilineMode | |
repl._line() if not multilineMode and backlog | |
backlog = '' | |
repl.setPrompt (newPrompt = if multilineMode then REPL_PROMPT_MULTILINE else REPL_PROMPT) | |
repl.prompt() | |
repl.output.cursorTo newPrompt.length + (repl.cursor = cursorPos) | |
# Handle Ctrl-d press at end of last line in multiline mode | |
repl.input.on 'keypress', (char, key) -> | |
return unless multilineMode and repl.line | |
# test for Ctrl-d | |
return unless key and key.ctrl and not key.meta and not key.shift and key.name is 'd' | |
multilineMode = off | |
repl._line() | |
repl.on 'attemptClose', -> | |
if multilineMode | |
multilineMode = off | |
repl.output.cursorTo 0 | |
repl.output.clearLine 1 | |
repl._onLine repl.line | |
return | |
if backlog or repl.line | |
backlog = '' | |
repl.historyIndex = -1 | |
repl.setPrompt REPL_PROMPT | |
repl.output.write '\n(^C again to quit)' | |
repl._line (repl.line = '') | |
else | |
repl.close() | |
repl.on 'close', -> | |
if backlog or repl.line | |
backlog = '' | |
repl.historyIndex = -1 | |
repl.setPrompt REPL_PROMPT | |
repl.output.write '\n(^C again to quit)' | |
repl._line (repl.line = '') | |
else | |
repl.output.write '\n' | |
repl.input.destroy() | |
repl.on 'line', run | |
repl.setPrompt REPL_PROMPT | |
repl.prompt() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment