summaryrefslogtreecommitdiff
path: root/lib/repl.js
blob: 52ae11c3a3b5a204ca39949f98bb1776dcc0a577 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
// A repl library that you can include in your own code to get a runtime
// interface to your program.
//
// var repl = require("/repl.js");
// repl.start("prompt> ");  // start repl on stdin
// net.createServer(function (socket) { // listen for unix socket connections and start repl on them
//   repl.start("node via Unix socket> ", socket);
// }).listen("/tmp/node-repl-sock");
// net.createServer(function (socket) { // listen for TCP socket connections and start repl on them
//   repl.start("node via TCP socket> ", socket);
// }).listen(5001);

// repl.start("node > ").context.foo = "stdin is fun";  // expose foo to repl context

var sys = require('sys');
var Script = process.binding('evals').Script;
var evalcx = Script.runInContext;
var path = require("path");
var rl = require('readline');
var context;

function cwdRequire (id) {
  if (id.match(/^\.\.\//) || id.match(/^\.\//)) {
    id = path.join(process.cwd(), id);
  }
  return require(id);
}
Object.keys(require).forEach(function (k) {
  cwdRequire[k] = require[k];
});

function resetContext() {
  context = Script.createContext();
  for (var i in global) context[i] = global[i];
  context.module = module;
  context.require = cwdRequire;
}


// Can overridden with custom print functions, such as `probe` or `eyes.js`
exports.writer = sys.inspect;

function REPLServer(prompt, stream) {
  var self = this;
  if (!context) resetContext();
  self.context = context;
  self.buffered_cmd = '';

  self.stream = stream || process.openStdin();
  self.prompt = prompt || "node> ";

  var rli = self.rli = rl.createInterface(self.stream);
  rli.setPrompt(self.prompt);

  self.stream.addListener("data", function (chunk) {
    rli.write(chunk);
  });

  rli.addListener('line', function (cmd) {
    cmd = trimWhitespace(cmd);

    var flushed = true;

    // Check to see if a REPL keyword was used. If it returns true,
    // display next prompt and return.
    if (self.parseREPLKeyword(cmd) === true) return;

    // The catchall for errors
    try {
      self.buffered_cmd += cmd;
      // This try is for determining if the command is complete, or should
      // continue onto the next line.
      try {
        // Use evalcx to supply the global context
        var ret = evalcx(self.buffered_cmd, context, "repl");
        if (ret !== undefined) {
          context._ = ret;
          flushed = self.stream.write(exports.writer(ret) + "\n");
        }

        self.buffered_cmd = '';
      } catch (e) {
        // instanceof doesn't work across context switches.
        if (!(e && e.constructor && e.constructor.name === "SyntaxError")) {
          throw e;
        }
      }
    } catch (e) {
      // On error: Print the error and clear the buffer
      if (e.stack) {
        flushed = self.stream.write(e.stack + "\n");
      } else {
        flushed = self.stream.write(e.toString() + "\n");
      }
      self.buffered_cmd = '';
    }

    // need to make sure the buffer is flushed before displaying the prompt
    // again. This is really ugly. Need to have callbacks from
    // net.Stream.write()
    if (flushed) {
      self.displayPrompt();
    } else {
      self.displayPromptOnDrain = true;
    }
  });

  self.stream.addListener('drain', function () {
    if (self.displayPromptOnDrain) {
      self.displayPrompt();
      self.displayPromptOnDrain = false;
    }
  });

  rli.addListener('close', function () {
    self.stream.destroy();
  });

  self.displayPrompt();
}
exports.REPLServer = REPLServer;

// prompt is a string to print on each line for the prompt,
// source is a stream to use for I/O, defaulting to stdin/stdout.
exports.start = function (prompt, source) {
  return new REPLServer(prompt, source);
};

REPLServer.prototype.displayPrompt = function () {
  this.rli.setPrompt(this.buffered_cmd.length ? '...   ' : this.prompt);
  this.rli.prompt();
};

// read a line from the stream, then eval it
REPLServer.prototype.readline = function (cmd) {
};

/**
 * 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
 */

REPLServer.prototype.parseREPLKeyword = function (cmd) {
  var self = this;

  switch (cmd) {
  case ".break":
    self.buffered_cmd = '';
    self.displayPrompt();
    return true;
  case ".clear":
    self.stream.write("Clearing context...\n");
    self.buffered_cmd = '';
    resetContext();
    self.displayPrompt();
    return true;
  case ".exit":
    self.stream.destroy();
    return true;
  case ".help":
    self.stream.write(".break\tSometimes you get stuck in a place you can't get out... This will get you out.\n");
    self.stream.write(".clear\tBreak, and also clear the local context.\n");
    self.stream.write(".exit\tExit the prompt\n");
    self.stream.write(".help\tShow repl options\n");
    self.displayPrompt();
    return true;
  }
  return false;
};

function trimWhitespace (cmd) {
  var trimmer = /^\s*(.+)\s*$/m,
    matches = trimmer.exec(cmd);

  if (matches && matches.length === 2) {
    return matches[1];
  }
}

/**
 * Converts commands that use var and function <name>() to use the
 * local exports.context when evaled. This provides a local context
 * on the REPL.
 *
 * @param {String} cmd The cmd to convert
 * @returns {String} The converted command
 */
REPLServer.prototype.convertToContext = function (cmd) {
  var self = this, matches,
    scopeVar = /^\s*var\s*([_\w\$]+)(.*)$/m,
    scopeFunc = /^\s*function\s*([_\w\$]+)/;

  // Replaces: var foo = "bar";  with: self.context.foo = bar;
  matches = scopeVar.exec(cmd);
  if (matches && matches.length === 3) {
    return "self.context." + matches[1] + matches[2];
  }

  // Replaces: function foo() {};  with: foo = function foo() {};
  matches = scopeFunc.exec(self.buffered_cmd);
  if (matches && matches.length === 2) {
    return matches[1] + " = " + self.buffered_cmd;
  }

  return cmd;
};