1
0
forked from Mirror/wren

Fall back to a simpler REPL if it seems like ANSI escapes won't work.

This commit is contained in:
Bob Nystrom
2016-05-23 19:59:01 -07:00
parent f47e128978
commit 33cf8f7b31
3 changed files with 405 additions and 227 deletions

View File

@ -1,6 +1,9 @@
import "meta" for Meta
import "io" for Stdin
import "os" for Platform
/// Abstract base class for the REPL. Manages the input line and history, but
/// does not render.
class Repl {
construct new() {
_cursor = 0
@ -8,78 +11,79 @@ class Repl {
_history = []
_historyIndex = 0
// Whether or not we allow ASCI escape sequences for showing colors and
// moving the cursor. When this is false, the REPL has reduced
// functionality.
_allowAnsi = false
}
cursor { _cursor }
cursor=(value) { _cursor = value }
line { _line }
line=(value) { _line = value }
run() {
Stdin.isRaw = true
refreshLine(false)
while (true) {
var byte = Stdin.readByte()
if (byte == Chars.ctrlA) {
_cursor = 0
} else if (byte == Chars.ctrlB) {
cursorLeft()
} else if (byte == Chars.ctrlC) {
System.print()
return
} else if (byte == Chars.ctrlD) {
// If the line is empty, Ctrl_D exits.
if (_line.isEmpty) {
System.print()
return
}
// Otherwise, it deletes the character after the cursor.
deleteRight()
} else if (byte == Chars.ctrlE) {
_cursor = _line.count
} else if (byte == Chars.ctrlF) {
cursorRight()
} else if (byte == Chars.tab) {
var completion = getCompletion()
if (completion != null) {
_line = _line + completion
_cursor = _line.count
}
} else if (byte == Chars.ctrlK) {
// Delete everything after the cursor.
_line = _line[0..._cursor]
} else if (byte == Chars.ctrlL) {
// Clear the screen.
System.write("\x1b[2J")
// Move cursor to top left.
System.write("\x1b[H")
} else if (byte == Chars.ctrlU) {
// Clear the line.
_line = ""
_cursor = 0
} else if (byte == Chars.ctrlN) {
nextHistory()
} else if (byte == Chars.ctrlP) {
previousHistory()
} else if (byte == Chars.escape) {
handleEscape()
} else if (byte == Chars.carriageReturn) {
executeInput()
} else if (byte == Chars.delete) {
deleteLeft()
} else if (byte >= Chars.space && byte <= Chars.tilde) {
insertChar(byte)
} else {
// TODO: Ctrl-T to swap chars.
// TODO: ESC H and F to move to beginning and end of line. (Both ESC
// [ and ESC 0 sequences?)
// TODO: Ctrl-W delete previous word.
// TODO: Completion.
// TODO: Other shortcuts?
System.print("Unhandled byte: %(byte)")
}
if (handleChar(byte)) break
refreshLine(true)
}
}
handleChar(byte) {
if (byte == Chars.ctrlC) {
System.print()
return true
} else if (byte == Chars.ctrlD) {
// If the line is empty, Ctrl_D exits.
if (_line.isEmpty) {
System.print()
return true
}
// Otherwise, it deletes the character after the cursor.
deleteRight()
} else if (byte == Chars.tab) {
var completion = getCompletion()
if (completion != null) {
_line = _line + completion
_cursor = _line.count
}
} else if (byte == Chars.ctrlU) {
// Clear the line.
_line = ""
_cursor = 0
} else if (byte == Chars.ctrlN) {
nextHistory()
} else if (byte == Chars.ctrlP) {
previousHistory()
} else if (byte == Chars.escape) {
var escapeType = Stdin.readByte()
var value = Stdin.readByte()
if (escapeType == Chars.leftBracket) {
// ESC [ sequence.
handleEscapeBracket(value)
} else {
// TODO: Handle ESC 0 sequences.
}
} else if (byte == Chars.carriageReturn) {
executeInput()
} else if (byte == Chars.delete) {
deleteLeft()
} else if (byte >= Chars.space && byte <= Chars.tilde) {
insertChar(byte)
} else {
// TODO: Other shortcuts?
System.print("Unhandled byte: %(byte)")
}
return false
}
/// Inserts the character with [byte] value at the current cursor position.
insertChar(byte) {
var char = String.fromCodePoint(byte)
@ -104,36 +108,14 @@ class Repl {
_line = _line[0..._cursor] + _line[(_cursor + 1)..-1]
}
handleEscape() {
var escapeType = Stdin.readByte()
var value = Stdin.readByte()
if (escapeType == Chars.leftBracket) {
// ESC [ sequence.
if (value == EscapeBracket.up) {
previousHistory()
} else if (value == EscapeBracket.down) {
nextHistory()
} else if (value == EscapeBracket.left) {
cursorLeft()
} else if (value == EscapeBracket.right) {
cursorRight()
}
} else {
// TODO: Handle ESC 0 sequences.
handleEscapeBracket(byte) {
if (byte == EscapeBracket.up) {
previousHistory()
} else if (byte == EscapeBracket.down) {
nextHistory()
}
}
/// Move the cursor left one character.
cursorLeft() {
if (_cursor > 0) _cursor = _cursor - 1
}
/// Move the cursor right one character.
cursorRight() {
// TODO: Take into account multi-byte characters?
if (_cursor < _line.count) _cursor = _cursor + 1
}
previousHistory() {
if (_historyIndex == 0) return
@ -159,9 +141,11 @@ class Repl {
// Remove the completion hint.
refreshLine(false)
// Add it to the history.
_history.add(_line)
_historyIndex = _history.count
// Add it to the history (if the line is interesting).
if (_line != "" && (_history.isEmpty || _history[-1] != _line)) {
_history.add(_line)
_historyIndex = _history.count
}
// Reset the current line.
var input = _line
@ -208,16 +192,13 @@ class Repl {
var result = fiber.try()
if (fiber.error == null) {
// TODO: Handle error in result.toString.
// TODO: Syntax color based on type? It might be nice to distinguish
// between string results versus stringified results. Otherwise, the
// user can't tell the difference between `true` and "true".
System.print("%(Color.brightWhite)%(result)%(Color.none)")
showResult(result)
return
}
}
System.print("%(Color.red)Runtime error: %(result)%(Color.none)")
// TODO: Print entire stack.
// TODO: Include callstack.
showRuntimeError("Runtime error: %(fiber.error)")
}
lex(line, includeWhitespace) {
@ -236,6 +217,113 @@ class Repl {
return tokens
}
/// Gets the best possible auto-completion for the current line, or null if
/// there is none. The completion is the remaining string to append to the
/// line, not the entire completed line.
getCompletion() {
if (_line.isEmpty) return null
// Only complete if the cursor is at the end.
if (_cursor != _line.count) return null
for (name in Meta.getModuleVariables("repl")) {
// TODO: Also allow completion if the line ends with an identifier but
// has other stuff before it.
if (name.startsWith(_line)) {
return name[_line.count..-1]
}
}
}
}
/// A reduced functionality REPL that doesn't use ANSI escape sequences.
class SimpleRepl is Repl {
construct new() {
super()
_erase = ""
}
refreshLine(showCompletion) {
// A carriage return just moves the cursor to the beginning of the line.
// We have to erase it manually. Since we can't use ANSI escapes, and we
// don't know how wide the terminal is, erase the longest line we've seen
// so far.
if (line.count > _erase.count) _erase = " " * line.count
System.write("\r %(_erase)")
// Show the prompt at the beginning of the line.
System.write("\r> ")
// Write the line.
System.write(line)
}
showResult(value) {
// TODO: Syntax color based on type? It might be nice to distinguish
// between string results versus stringified results. Otherwise, the
// user can't tell the difference between `true` and "true".
System.print(value)
}
showRuntimeError(message) {
System.print(message)
}
}
class AnsiRepl is Repl {
construct new() {
super()
}
handleChar(byte) {
if (byte == Chars.ctrlA) {
cursor = 0
} else if (byte == Chars.ctrlB) {
cursorLeft()
} else if (byte == Chars.ctrlE) {
cursor = line.count
} else if (byte == Chars.ctrlF) {
cursorRight()
} else if (byte == Chars.ctrlK) {
// Delete everything after the cursor.
line = line[0...cursor]
} else if (byte == Chars.ctrlL) {
// Clear the screen.
System.write("\x1b[2J")
// Move cursor to top left.
System.write("\x1b[H")
} else {
// TODO: Ctrl-T to swap chars.
// TODO: ESC H and F to move to beginning and end of line. (Both ESC
// [ and ESC 0 sequences?)
// TODO: Ctrl-W delete previous word.
return super.handleChar(byte)
}
return false
}
handleEscapeBracket(byte) {
if (byte == EscapeBracket.left) {
cursorLeft()
} else if (byte == EscapeBracket.right) {
cursorRight()
}
super.handleEscapeBracket(byte)
}
/// Move the cursor left one character.
cursorLeft() {
if (cursor > 0) cursor = cursor - 1
}
/// Move the cursor right one character.
cursorRight() {
// TODO: Take into account multi-byte characters?
if (cursor < line.count) cursor = cursor + 1
}
refreshLine(showCompletion) {
// Erase the whole line.
System.write("\x1b[2K")
@ -246,7 +334,7 @@ class Repl {
System.write(Color.none)
// Syntax highlight the line.
for (token in lex(_line, true)) {
for (token in lex(line, true)) {
if (token.type == Token.eof) break
System.write(TOKEN_COLORS[token.type])
@ -262,23 +350,19 @@ class Repl {
}
// Position the cursor.
System.write("\r\x1b[%(2 + _cursor)C")
System.write("\r\x1b[%(2 + cursor)C")
}
/// Gets the best possible auto-completion for the current line, or null if
/// there is none. The completion is the remaining string to append to the
/// line, not the entire completed line.
getCompletion() {
if (_line.isEmpty) return null
showResult(value) {
// TODO: Syntax color based on type? It might be nice to distinguish
// between string results versus stringified results. Otherwise, the
// user can't tell the difference between `true` and "true".
System.print("%(Color.brightWhite)%(value)%(Color.none)")
}
// Only complete if the cursor is at the end.
if (_cursor != _line.count) return null
for (name in Meta.getModuleVariables("repl")) {
if (name.startsWith(_line)) {
return name[_line.count..-1]
}
}
showRuntimeError(message) {
System.print("%(Color.red)%(message)%(Color.none)")
// TODO: Print entire stack.
}
}
@ -836,5 +920,10 @@ class Lexer {
makeToken(type) { Token.new(_source, type, _start, _current - _start) }
}
// Fire up the REPL.
Repl.new().run()
// Fire up the REPL. We use ANSI when talking to a POSIX TTY.
if (Platform.isPosix && Stdin.isTerminal) {
AnsiRepl.new().run()
} else {
// ANSI escape sequences probably aren't supported, so degrade.
SimpleRepl.new().run()
}

View File

@ -2,7 +2,10 @@
static const char* replModuleSource =
"import \"meta\" for Meta\n"
"import \"io\" for Stdin\n"
"import \"os\" for Platform\n"
"\n"
"/// Abstract base class for the REPL. Manages the input line and history, but\n"
"/// does not render.\n"
"class Repl {\n"
" construct new() {\n"
" _cursor = 0\n"
@ -10,78 +13,79 @@ static const char* replModuleSource =
"\n"
" _history = []\n"
" _historyIndex = 0\n"
"\n"
" // Whether or not we allow ASCI escape sequences for showing colors and\n"
" // moving the cursor. When this is false, the REPL has reduced\n"
" // functionality.\n"
" _allowAnsi = false\n"
" }\n"
"\n"
" cursor { _cursor }\n"
" cursor=(value) { _cursor = value }\n"
" line { _line }\n"
" line=(value) { _line = value }\n"
"\n"
" run() {\n"
" Stdin.isRaw = true\n"
" refreshLine(false)\n"
"\n"
" while (true) {\n"
" var byte = Stdin.readByte()\n"
" if (byte == Chars.ctrlA) {\n"
" _cursor = 0\n"
" } else if (byte == Chars.ctrlB) {\n"
" cursorLeft()\n"
" } else if (byte == Chars.ctrlC) {\n"
" System.print()\n"
" return\n"
" } else if (byte == Chars.ctrlD) {\n"
" // If the line is empty, Ctrl_D exits.\n"
" if (_line.isEmpty) {\n"
" System.print()\n"
" return\n"
" }\n"
"\n"
" // Otherwise, it deletes the character after the cursor.\n"
" deleteRight()\n"
" } else if (byte == Chars.ctrlE) {\n"
" _cursor = _line.count\n"
" } else if (byte == Chars.ctrlF) {\n"
" cursorRight()\n"
" } else if (byte == Chars.tab) {\n"
" var completion = getCompletion()\n"
" if (completion != null) {\n"
" _line = _line + completion\n"
" _cursor = _line.count\n"
" }\n"
" } else if (byte == Chars.ctrlK) {\n"
" // Delete everything after the cursor.\n"
" _line = _line[0..._cursor]\n"
" } else if (byte == Chars.ctrlL) {\n"
" // Clear the screen.\n"
" System.write(\"\x1b[2J\")\n"
" // Move cursor to top left.\n"
" System.write(\"\x1b[H\")\n"
" } else if (byte == Chars.ctrlU) {\n"
" // Clear the line.\n"
" _line = \"\"\n"
" _cursor = 0\n"
" } else if (byte == Chars.ctrlN) {\n"
" nextHistory()\n"
" } else if (byte == Chars.ctrlP) {\n"
" previousHistory()\n"
" } else if (byte == Chars.escape) {\n"
" handleEscape()\n"
" } else if (byte == Chars.carriageReturn) {\n"
" executeInput()\n"
" } else if (byte == Chars.delete) {\n"
" deleteLeft()\n"
" } else if (byte >= Chars.space && byte <= Chars.tilde) {\n"
" insertChar(byte)\n"
" } else {\n"
" // TODO: Ctrl-T to swap chars.\n"
" // TODO: ESC H and F to move to beginning and end of line. (Both ESC\n"
" // [ and ESC 0 sequences?)\n"
" // TODO: Ctrl-W delete previous word.\n"
" // TODO: Completion.\n"
" // TODO: Other shortcuts?\n"
" System.print(\"Unhandled byte: %(byte)\")\n"
" }\n"
"\n"
" if (handleChar(byte)) break\n"
" refreshLine(true)\n"
" }\n"
" }\n"
"\n"
" handleChar(byte) {\n"
" if (byte == Chars.ctrlC) {\n"
" System.print()\n"
" return true\n"
" } else if (byte == Chars.ctrlD) {\n"
" // If the line is empty, Ctrl_D exits.\n"
" if (_line.isEmpty) {\n"
" System.print()\n"
" return true\n"
" }\n"
"\n"
" // Otherwise, it deletes the character after the cursor.\n"
" deleteRight()\n"
" } else if (byte == Chars.tab) {\n"
" var completion = getCompletion()\n"
" if (completion != null) {\n"
" _line = _line + completion\n"
" _cursor = _line.count\n"
" }\n"
" } else if (byte == Chars.ctrlU) {\n"
" // Clear the line.\n"
" _line = \"\"\n"
" _cursor = 0\n"
" } else if (byte == Chars.ctrlN) {\n"
" nextHistory()\n"
" } else if (byte == Chars.ctrlP) {\n"
" previousHistory()\n"
" } else if (byte == Chars.escape) {\n"
" var escapeType = Stdin.readByte()\n"
" var value = Stdin.readByte()\n"
" if (escapeType == Chars.leftBracket) {\n"
" // ESC [ sequence.\n"
" handleEscapeBracket(value)\n"
" } else {\n"
" // TODO: Handle ESC 0 sequences.\n"
" }\n"
" } else if (byte == Chars.carriageReturn) {\n"
" executeInput()\n"
" } else if (byte == Chars.delete) {\n"
" deleteLeft()\n"
" } else if (byte >= Chars.space && byte <= Chars.tilde) {\n"
" insertChar(byte)\n"
" } else {\n"
" // TODO: Other shortcuts?\n"
" System.print(\"Unhandled byte: %(byte)\")\n"
" }\n"
"\n"
" return false\n"
" }\n"
"\n"
" /// Inserts the character with [byte] value at the current cursor position.\n"
" insertChar(byte) {\n"
" var char = String.fromCodePoint(byte)\n"
@ -106,36 +110,14 @@ static const char* replModuleSource =
" _line = _line[0..._cursor] + _line[(_cursor + 1)..-1]\n"
" }\n"
"\n"
" handleEscape() {\n"
" var escapeType = Stdin.readByte()\n"
" var value = Stdin.readByte()\n"
" if (escapeType == Chars.leftBracket) {\n"
" // ESC [ sequence.\n"
" if (value == EscapeBracket.up) {\n"
" previousHistory()\n"
" } else if (value == EscapeBracket.down) {\n"
" nextHistory()\n"
" } else if (value == EscapeBracket.left) {\n"
" cursorLeft()\n"
" } else if (value == EscapeBracket.right) {\n"
" cursorRight()\n"
" }\n"
" } else {\n"
" // TODO: Handle ESC 0 sequences.\n"
" handleEscapeBracket(byte) {\n"
" if (byte == EscapeBracket.up) {\n"
" previousHistory()\n"
" } else if (byte == EscapeBracket.down) {\n"
" nextHistory()\n"
" }\n"
" }\n"
"\n"
" /// Move the cursor left one character.\n"
" cursorLeft() {\n"
" if (_cursor > 0) _cursor = _cursor - 1\n"
" }\n"
"\n"
" /// Move the cursor right one character.\n"
" cursorRight() {\n"
" // TODO: Take into account multi-byte characters?\n"
" if (_cursor < _line.count) _cursor = _cursor + 1\n"
" }\n"
"\n"
" previousHistory() {\n"
" if (_historyIndex == 0) return\n"
"\n"
@ -161,9 +143,11 @@ static const char* replModuleSource =
" // Remove the completion hint.\n"
" refreshLine(false)\n"
"\n"
" // Add it to the history.\n"
" _history.add(_line)\n"
" _historyIndex = _history.count\n"
" // Add it to the history (if the line is interesting).\n"
" if (_line != \"\" && (_history.isEmpty || _history[-1] != _line)) {\n"
" _history.add(_line)\n"
" _historyIndex = _history.count\n"
" }\n"
"\n"
" // Reset the current line.\n"
" var input = _line\n"
@ -210,16 +194,13 @@ static const char* replModuleSource =
" var result = fiber.try()\n"
" if (fiber.error == null) {\n"
" // TODO: Handle error in result.toString.\n"
" // TODO: Syntax color based on type? It might be nice to distinguish\n"
" // between string results versus stringified results. Otherwise, the\n"
" // user can't tell the difference between `true` and \"true\".\n"
" System.print(\"%(Color.brightWhite)%(result)%(Color.none)\")\n"
" showResult(result)\n"
" return\n"
" }\n"
" }\n"
"\n"
" System.print(\"%(Color.red)Runtime error: %(result)%(Color.none)\")\n"
" // TODO: Print entire stack.\n"
" // TODO: Include callstack.\n"
" showRuntimeError(\"Runtime error: %(fiber.error)\")\n"
" }\n"
"\n"
" lex(line, includeWhitespace) {\n"
@ -238,6 +219,113 @@ static const char* replModuleSource =
" return tokens\n"
" }\n"
"\n"
" /// Gets the best possible auto-completion for the current line, or null if\n"
" /// there is none. The completion is the remaining string to append to the\n"
" /// line, not the entire completed line.\n"
" getCompletion() {\n"
" if (_line.isEmpty) return null\n"
"\n"
" // Only complete if the cursor is at the end.\n"
" if (_cursor != _line.count) return null\n"
"\n"
" for (name in Meta.getModuleVariables(\"repl\")) {\n"
" // TODO: Also allow completion if the line ends with an identifier but\n"
" // has other stuff before it.\n"
" if (name.startsWith(_line)) {\n"
" return name[_line.count..-1]\n"
" }\n"
" }\n"
" }\n"
"}\n"
"\n"
"/// A reduced functionality REPL that doesn't use ANSI escape sequences.\n"
"class SimpleRepl is Repl {\n"
" construct new() {\n"
" super()\n"
" _erase = \"\"\n"
" }\n"
"\n"
" refreshLine(showCompletion) {\n"
" // A carriage return just moves the cursor to the beginning of the line.\n"
" // We have to erase it manually. Since we can't use ANSI escapes, and we\n"
" // don't know how wide the terminal is, erase the longest line we've seen\n"
" // so far.\n"
" if (line.count > _erase.count) _erase = \" \" * line.count\n"
" System.write(\"\r %(_erase)\")\n"
"\n"
" // Show the prompt at the beginning of the line.\n"
" System.write(\"\r> \")\n"
"\n"
" // Write the line.\n"
" System.write(line)\n"
" }\n"
"\n"
" showResult(value) {\n"
" // TODO: Syntax color based on type? It might be nice to distinguish\n"
" // between string results versus stringified results. Otherwise, the\n"
" // user can't tell the difference between `true` and \"true\".\n"
" System.print(value)\n"
" }\n"
"\n"
" showRuntimeError(message) {\n"
" System.print(message)\n"
" }\n"
"}\n"
"\n"
"class AnsiRepl is Repl {\n"
" construct new() {\n"
" super()\n"
" }\n"
"\n"
" handleChar(byte) {\n"
" if (byte == Chars.ctrlA) {\n"
" cursor = 0\n"
" } else if (byte == Chars.ctrlB) {\n"
" cursorLeft()\n"
" } else if (byte == Chars.ctrlE) {\n"
" cursor = line.count\n"
" } else if (byte == Chars.ctrlF) {\n"
" cursorRight()\n"
" } else if (byte == Chars.ctrlK) {\n"
" // Delete everything after the cursor.\n"
" line = line[0...cursor]\n"
" } else if (byte == Chars.ctrlL) {\n"
" // Clear the screen.\n"
" System.write(\"\x1b[2J\")\n"
" // Move cursor to top left.\n"
" System.write(\"\x1b[H\")\n"
" } else {\n"
" // TODO: Ctrl-T to swap chars.\n"
" // TODO: ESC H and F to move to beginning and end of line. (Both ESC\n"
" // [ and ESC 0 sequences?)\n"
" // TODO: Ctrl-W delete previous word.\n"
" return super.handleChar(byte)\n"
" }\n"
"\n"
" return false\n"
" }\n"
"\n"
" handleEscapeBracket(byte) {\n"
" if (byte == EscapeBracket.left) {\n"
" cursorLeft()\n"
" } else if (byte == EscapeBracket.right) {\n"
" cursorRight()\n"
" }\n"
"\n"
" super.handleEscapeBracket(byte)\n"
" }\n"
"\n"
" /// Move the cursor left one character.\n"
" cursorLeft() {\n"
" if (cursor > 0) cursor = cursor - 1\n"
" }\n"
"\n"
" /// Move the cursor right one character.\n"
" cursorRight() {\n"
" // TODO: Take into account multi-byte characters?\n"
" if (cursor < line.count) cursor = cursor + 1\n"
" }\n"
"\n"
" refreshLine(showCompletion) {\n"
" // Erase the whole line.\n"
" System.write(\"\x1b[2K\")\n"
@ -248,7 +336,7 @@ static const char* replModuleSource =
" System.write(Color.none)\n"
"\n"
" // Syntax highlight the line.\n"
" for (token in lex(_line, true)) {\n"
" for (token in lex(line, true)) {\n"
" if (token.type == Token.eof) break\n"
"\n"
" System.write(TOKEN_COLORS[token.type])\n"
@ -264,23 +352,19 @@ static const char* replModuleSource =
" }\n"
"\n"
" // Position the cursor.\n"
" System.write(\"\r\x1b[%(2 + _cursor)C\")\n"
" System.write(\"\r\x1b[%(2 + cursor)C\")\n"
" }\n"
"\n"
" /// Gets the best possible auto-completion for the current line, or null if\n"
" /// there is none. The completion is the remaining string to append to the\n"
" /// line, not the entire completed line.\n"
" getCompletion() {\n"
" if (_line.isEmpty) return null\n"
" showResult(value) {\n"
" // TODO: Syntax color based on type? It might be nice to distinguish\n"
" // between string results versus stringified results. Otherwise, the\n"
" // user can't tell the difference between `true` and \"true\".\n"
" System.print(\"%(Color.brightWhite)%(value)%(Color.none)\")\n"
" }\n"
"\n"
" // Only complete if the cursor is at the end.\n"
" if (_cursor != _line.count) return null\n"
"\n"
" for (name in Meta.getModuleVariables(\"repl\")) {\n"
" if (name.startsWith(_line)) {\n"
" return name[_line.count..-1]\n"
" }\n"
" }\n"
" showRuntimeError(message) {\n"
" System.print(\"%(Color.red)%(message)%(Color.none)\")\n"
" // TODO: Print entire stack.\n"
" }\n"
"}\n"
"\n"
@ -838,5 +922,10 @@ static const char* replModuleSource =
" makeToken(type) { Token.new(_source, type, _start, _current - _start) }\n"
"}\n"
"\n"
"// Fire up the REPL.\n"
"Repl.new().run()\n";
"// Fire up the REPL. We use ANSI when talking to a POSIX TTY.\n"
"if (Platform.isPosix && Stdin.isTerminal) {\n"
" AnsiRepl.new().run()\n"
"} else {\n"
" // ANSI escape sequences probably aren't supported, so degrade.\n"
" SimpleRepl.new().run()\n"
"}\n";

View File

@ -46,7 +46,7 @@ void metaGetModuleVariables(WrenVM* vm) {
return;
}
ObjModule* module = AS_MODULE(moduleValue);
ObjModule* module = AS_MODULE(moduleValue);
ObjList* names = wrenNewList(vm, module->variableNames.count);
vm->apiStack[0] = OBJ_VAL(names);