diff --git a/src/module/repl.wren b/src/module/repl.wren index fe6f078a..cad9e667 100644 --- a/src/module/repl.wren +++ b/src/module/repl.wren @@ -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() +} diff --git a/src/module/repl.wren.inc b/src/module/repl.wren.inc index 6a374627..5e07831b 100644 --- a/src/module/repl.wren.inc +++ b/src/module/repl.wren.inc @@ -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"; diff --git a/src/optional/wren_opt_meta.c b/src/optional/wren_opt_meta.c index ccc33116..5f37a8e5 100644 --- a/src/optional/wren_opt_meta.c +++ b/src/optional/wren_opt_meta.c @@ -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);