aboutsummaryrefslogtreecommitdiff
path: root/lib/repl.js
blob: 6188d7a3b3cb662c56df3fd1adcf065da2f27ffa (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
// A repl library that you can include in your own code to get a runtime
// interface to your program. Just require("/repl.js").

var sys = require('sys');

var buffered_cmd = '';
var trimmer = /^\s*(.+)\s*$/m;
var scopedVar = /^\s*var\s*([_\w\$]+)(.*)$/m;
var scopeFunc = /^\s*function\s*([_\w\$]+)/;

exports.scope = {};
exports.prompt = "node> ";
// Can overridden with custom print functions, such as `probe` or `eyes.js`
exports.writer = sys.p;

var stdin;

exports.start = function (prompt) {
  if (prompt !== undefined) {
    exports.prompt = prompt;
  }

  stdin = process.openStdin();
  stdin.setEncoding('utf8');
  stdin.addListener("data", readline);
  displayPrompt();
}

/**
 * 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 += "\n" + cmd;
    // This try is for determining if the command is complete, or should
    // continue onto the next line.
    try {
      buffered_cmd = convertToScope(buffered_cmd);

      // Scope the readline with exports.scope to provide "local" vars
      with (exports.scope) {
        var ret = eval(buffered_cmd);
        if (ret !== undefined) {
          exports.scope['_'] = ret;
          exports.writer(ret);
        }
      }

      buffered_cmd = '';
    } catch (e) {
      if (!(e instanceof SyntaxError)) throw e;
    }
  } catch (e) {
    // On error: Print the error and clear the buffer
    if (e.stack) {
      sys.puts(e.stack);
    } else {
      sys.puts(e.toString());
    }
    buffered_cmd = '';
  }

  displayPrompt();
}


/**
 * Used to display the prompt.
 */
function displayPrompt () {
  sys.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":
    sys.puts("Clearing Scope...");
    buffered_cmd = '';
    exports.scope = {};
    displayPrompt();
    return true;
  case ".exit":
    stdin.close();
    return true;
  case ".help":
    sys.puts(".break\tSometimes you get stuck in a place you can't get out... This will get you out.");
    sys.puts(".clear\tBreak, and also clear the local scope.");
    sys.puts(".exit\tExit the prompt");
    sys.puts(".help\tShow repl options");
    displayPrompt();
    return true;
  }
  return false;
}

/**
 * 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 <name>() 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;
}