scripted-engine/src/logic/wren/module/repl.wren.inc

957 lines
29 KiB
C++

// Generated automatically from src/module/repl.wren. Do not edit.
static const char* replModuleSource =
"import \"meta\" for Meta\n"
"import \"io\" for Stdin, Stdout\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"
" _line = \"\"\n"
"\n"
" _history = []\n"
" _historyIndex = 0\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 (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 if (byte == Chars.ctrlW) { // Handle Ctrl+w\n"
" // Delete trailing spaces\n"
" while (_cursor != 0 && _line[_cursor - 1] == \" \") {\n"
" deleteLeft()\n"
" }\n"
" // Delete until the next space\n"
" while (_cursor != 0 && _line[_cursor - 1] != \" \") {\n"
" deleteLeft()\n"
" }\n"
" } else {\n"
" // TODO: Other shortcuts?\n"
" System.print(\"Unhandled key-code [dec]: %(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"
" _line = _line[0..._cursor] + char + _line[_cursor..-1]\n"
" _cursor = _cursor + 1\n"
" }\n"
"\n"
" /// Deletes the character before the cursor, if any.\n"
" deleteLeft() {\n"
" if (_cursor == 0) return\n"
"\n"
" // Delete the character before the cursor.\n"
" _line = _line[0...(_cursor - 1)] + _line[_cursor..-1]\n"
" _cursor = _cursor - 1\n"
" }\n"
"\n"
" /// Deletes the character after the cursor, if any.\n"
" deleteRight() {\n"
" if (_cursor == _line.count) return\n"
"\n"
" // Delete the character after the cursor.\n"
" _line = _line[0..._cursor] + _line[(_cursor + 1)..-1]\n"
" }\n"
"\n"
" handleEscapeBracket(byte) {\n"
" if (byte == EscapeBracket.up) {\n"
" previousHistory()\n"
" } else if (byte == EscapeBracket.down) {\n"
" nextHistory()\n"
" } else if (byte == EscapeBracket.delete) {\n"
" deleteRight()\n"
" // Consume extra 126 character generated by delete\n"
" Stdin.readByte()\n"
" } else if (byte == EscapeBracket.end) {\n"
" _cursor = _line.count\n"
" } else if (byte == EscapeBracket.home) {\n"
" _cursor = 0\n"
" }\n"
" }\n"
"\n"
" previousHistory() {\n"
" if (_historyIndex == 0) return\n"
"\n"
" _historyIndex = _historyIndex - 1\n"
" _line = _history[_historyIndex]\n"
" _cursor = _line.count\n"
" }\n"
"\n"
" nextHistory() {\n"
" if (_historyIndex >= _history.count) return\n"
"\n"
" _historyIndex = _historyIndex + 1\n"
" if (_historyIndex < _history.count) {\n"
" _line = _history[_historyIndex]\n"
" _cursor = _line.count\n"
" } else {\n"
" _line = \"\"\n"
" _cursor = 0\n"
" }\n"
" }\n"
"\n"
" executeInput() {\n"
" // Remove the completion hint.\n"
" refreshLine(false)\n"
"\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"
" _line = \"\"\n"
" _cursor = 0\n"
"\n"
" System.print()\n"
"\n"
" // Guess if it looks like a statement or expression. If it looks like an\n"
" // expression, we try to print the result.\n"
" var token = lexFirst(input)\n"
"\n"
" // No code, so do nothing.\n"
" if (token == null) return\n"
"\n"
" var isStatement =\n"
" token.type == Token.breakKeyword ||\n"
" token.type == Token.classKeyword ||\n"
" token.type == Token.forKeyword ||\n"
" token.type == Token.foreignKeyword ||\n"
" token.type == Token.ifKeyword ||\n"
" token.type == Token.importKeyword ||\n"
" token.type == Token.returnKeyword ||\n"
" token.type == Token.varKeyword ||\n"
" token.type == Token.whileKeyword\n"
"\n"
" var closure\n"
" if (isStatement) {\n"
" closure = Meta.compile(input)\n"
" } else {\n"
" closure = Meta.compileExpression(input)\n"
" }\n"
"\n"
" // Stop if there was a compile error.\n"
" if (closure == null) return\n"
"\n"
" var fiber = Fiber.new(closure)\n"
"\n"
" var result = fiber.try()\n"
" if (fiber.error != null) {\n"
" // TODO: Include callstack.\n"
" showRuntimeError(\"Runtime error: %(fiber.error)\")\n"
" return\n"
" }\n"
"\n"
" if (!isStatement) {\n"
" showResult(result)\n"
" }\n"
" }\n"
"\n"
" lex(line, includeWhitespace) {\n"
" var lexer = Lexer.new(line)\n"
" var tokens = []\n"
" while (true) {\n"
" var token = lexer.readToken()\n"
" if (token.type == Token.eof) break\n"
"\n"
" if (includeWhitespace ||\n"
" (token.type != Token.comment && token.type != Token.whitespace)) {\n"
" tokens.add(token)\n"
" }\n"
" }\n"
"\n"
" return tokens\n"
" }\n"
"\n"
" lexFirst(line) {\n"
" var lexer = Lexer.new(line)\n"
" while (true) {\n"
" var token = lexer.readToken()\n"
" if (token.type == Token.eof) return null\n"
"\n"
" if (token.type != Token.comment && token.type != Token.whitespace) {\n"
" return token\n"
" }\n"
" }\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"
" Stdout.flush()\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"
"\n"
" // Show the prompt at the beginning of the line.\n"
" System.write(Color.gray)\n"
" System.write(\"\r> \")\n"
" System.write(Color.none)\n"
"\n"
" // Syntax highlight the line.\n"
" for (token in lex(line, true)) {\n"
" if (token.type == Token.eof) break\n"
"\n"
" System.write(TOKEN_COLORS[token.type])\n"
" System.write(token.text)\n"
" System.write(Color.none)\n"
" }\n"
"\n"
" if (showCompletion) {\n"
" var completion = getCompletion()\n"
" if (completion != null) {\n"
" System.write(\"%(Color.gray)%(completion)%(Color.none)\")\n"
" }\n"
" }\n"
"\n"
" // Position the cursor.\n"
" System.write(\"\r\x1b[%(2 + cursor)C\")\n"
" Stdout.flush()\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(\"%(Color.brightWhite)%(value)%(Color.none)\")\n"
" }\n"
"\n"
" showRuntimeError(message) {\n"
" System.print(\"%(Color.red)%(message)%(Color.none)\")\n"
" // TODO: Print entire stack.\n"
" }\n"
"}\n"
"\n"
"/// ANSI color escape sequences.\n"
"class Color {\n"
" static none { \"\x1b[0m\" }\n"
" static black { \"\x1b[30m\" }\n"
" static red { \"\x1b[31m\" }\n"
" static green { \"\x1b[32m\" }\n"
" static yellow { \"\x1b[33m\" }\n"
" static blue { \"\x1b[34m\" }\n"
" static magenta { \"\x1b[35m\" }\n"
" static cyan { \"\x1b[36m\" }\n"
" static white { \"\x1b[37m\" }\n"
"\n"
" static gray { \"\x1b[30;1m\" }\n"
" static pink { \"\x1b[31;1m\" }\n"
" static brightWhite { \"\x1b[37;1m\" }\n"
"}\n"
"\n"
"/// Utilities for working with characters.\n"
"class Chars {\n"
" static ctrlA { 0x01 }\n"
" static ctrlB { 0x02 }\n"
" static ctrlC { 0x03 }\n"
" static ctrlD { 0x04 }\n"
" static ctrlE { 0x05 }\n"
" static ctrlF { 0x06 }\n"
" static tab { 0x09 }\n"
" static lineFeed { 0x0a }\n"
" static ctrlK { 0x0b }\n"
" static ctrlL { 0x0c }\n"
" static carriageReturn { 0x0d }\n"
" static ctrlN { 0x0e }\n"
" static ctrlP { 0x10 }\n"
" static ctrlU { 0x15 }\n"
" static ctrlW { 0x17 }\n"
" static escape { 0x1b }\n"
" static space { 0x20 }\n"
" static bang { 0x21 }\n"
" static quote { 0x22 }\n"
" static percent { 0x25 }\n"
" static amp { 0x26 }\n"
" static leftParen { 0x28 }\n"
" static rightParen { 0x29 }\n"
" static star { 0x2a }\n"
" static plus { 0x2b }\n"
" static comma { 0x2c }\n"
" static minus { 0x2d }\n"
" static dot { 0x2e }\n"
" static slash { 0x2f }\n"
"\n"
" static zero { 0x30 }\n"
" static nine { 0x39 }\n"
"\n"
" static colon { 0x3a }\n"
" static less { 0x3c }\n"
" static equal { 0x3d }\n"
" static greater { 0x3e }\n"
" static question { 0x3f }\n"
"\n"
" static upperA { 0x41 }\n"
" static upperF { 0x46 }\n"
" static upperZ { 0x5a }\n"
"\n"
" static leftBracket { 0x5b }\n"
" static backslash { 0x5c }\n"
" static rightBracket { 0x5d }\n"
" static caret { 0x5e }\n"
" static underscore { 0x5f }\n"
"\n"
" static lowerA { 0x61 }\n"
" static lowerF { 0x66 }\n"
" static lowerX { 0x78 }\n"
" static lowerZ { 0x7a }\n"
"\n"
" static leftBrace { 0x7b }\n"
" static pipe { 0x7c }\n"
" static rightBrace { 0x7d }\n"
" static tilde { 0x7e }\n"
" static delete { 0x7f }\n"
"\n"
" static isAlpha(c) {\n"
" return c >= lowerA && c <= lowerZ ||\n"
" c >= upperA && c <= upperZ ||\n"
" c == underscore\n"
" }\n"
"\n"
" static isDigit(c) { c >= zero && c <= nine }\n"
"\n"
" static isAlphaNumeric(c) { isAlpha(c) || isDigit(c) }\n"
"\n"
" static isHexDigit(c) {\n"
" return c >= zero && c <= nine ||\n"
" c >= lowerA && c <= lowerF ||\n"
" c >= upperA && c <= upperF\n"
" }\n"
"\n"
" static isLowerAlpha(c) { c >= lowerA && c <= lowerZ }\n"
"\n"
" static isWhitespace(c) { c == space || c == tab || c == carriageReturn }\n"
"}\n"
"\n"
"class EscapeBracket {\n"
" static delete { 0x33 }\n"
" static up { 0x41 }\n"
" static down { 0x42 }\n"
" static right { 0x43 }\n"
" static left { 0x44 }\n"
" static end { 0x46 }\n"
" static home { 0x48 }\n"
"}\n"
"\n"
"class Token {\n"
" // Punctuators.\n"
" static leftParen { \"leftParen\" }\n"
" static rightParen { \"rightParen\" }\n"
" static leftBracket { \"leftBracket\" }\n"
" static rightBracket { \"rightBracket\" }\n"
" static leftBrace { \"leftBrace\" }\n"
" static rightBrace { \"rightBrace\" }\n"
" static colon { \"colon\" }\n"
" static dot { \"dot\" }\n"
" static dotDot { \"dotDot\" }\n"
" static dotDotDot { \"dotDotDot\" }\n"
" static comma { \"comma\" }\n"
" static star { \"star\" }\n"
" static slash { \"slash\" }\n"
" static percent { \"percent\" }\n"
" static plus { \"plus\" }\n"
" static minus { \"minus\" }\n"
" static pipe { \"pipe\" }\n"
" static pipePipe { \"pipePipe\" }\n"
" static caret { \"caret\" }\n"
" static amp { \"amp\" }\n"
" static ampAmp { \"ampAmp\" }\n"
" static question { \"question\" }\n"
" static bang { \"bang\" }\n"
" static tilde { \"tilde\" }\n"
" static equal { \"equal\" }\n"
" static less { \"less\" }\n"
" static lessEqual { \"lessEqual\" }\n"
" static lessLess { \"lessLess\" }\n"
" static greater { \"greater\" }\n"
" static greaterEqual { \"greaterEqual\" }\n"
" static greaterGreater { \"greaterGreater\" }\n"
" static equalEqual { \"equalEqual\" }\n"
" static bangEqual { \"bangEqual\" }\n"
"\n"
" // Keywords.\n"
" static breakKeyword { \"break\" }\n"
" static classKeyword { \"class\" }\n"
" static constructKeyword { \"construct\" }\n"
" static elseKeyword { \"else\" }\n"
" static falseKeyword { \"false\" }\n"
" static forKeyword { \"for\" }\n"
" static foreignKeyword { \"foreign\" }\n"
" static ifKeyword { \"if\" }\n"
" static importKeyword { \"import\" }\n"
" static inKeyword { \"in\" }\n"
" static isKeyword { \"is\" }\n"
" static nullKeyword { \"null\" }\n"
" static returnKeyword { \"return\" }\n"
" static staticKeyword { \"static\" }\n"
" static superKeyword { \"super\" }\n"
" static thisKeyword { \"this\" }\n"
" static trueKeyword { \"true\" }\n"
" static varKeyword { \"var\" }\n"
" static whileKeyword { \"while\" }\n"
"\n"
" static field { \"field\" }\n"
" static name { \"name\" }\n"
" static number { \"number\" }\n"
" static string { \"string\" }\n"
" static interpolation { \"interpolation\" }\n"
" static comment { \"comment\" }\n"
" static whitespace { \"whitespace\" }\n"
" static line { \"line\" }\n"
" static error { \"error\" }\n"
" static eof { \"eof\" }\n"
"\n"
" construct new(source, type, start, length) {\n"
" _source = source\n"
" _type = type\n"
" _start = start\n"
" _length = length\n"
" }\n"
"\n"
" type { _type }\n"
" text { _source[_start...(_start + _length)] }\n"
"\n"
" start { _start }\n"
" length { _length }\n"
"\n"
" toString { text }\n"
"}\n"
"\n"
"var KEYWORDS = {\n"
" \"break\": Token.breakKeyword,\n"
" \"class\": Token.classKeyword,\n"
" \"construct\": Token.constructKeyword,\n"
" \"else\": Token.elseKeyword,\n"
" \"false\": Token.falseKeyword,\n"
" \"for\": Token.forKeyword,\n"
" \"foreign\": Token.foreignKeyword,\n"
" \"if\": Token.ifKeyword,\n"
" \"import\": Token.importKeyword,\n"
" \"in\": Token.inKeyword,\n"
" \"is\": Token.isKeyword,\n"
" \"null\": Token.nullKeyword,\n"
" \"return\": Token.returnKeyword,\n"
" \"static\": Token.staticKeyword,\n"
" \"super\": Token.superKeyword,\n"
" \"this\": Token.thisKeyword,\n"
" \"true\": Token.trueKeyword,\n"
" \"var\": Token.varKeyword,\n"
" \"while\": Token.whileKeyword\n"
"}\n"
"\n"
"var TOKEN_COLORS = {\n"
" Token.leftParen: Color.gray,\n"
" Token.rightParen: Color.gray,\n"
" Token.leftBracket: Color.gray,\n"
" Token.rightBracket: Color.gray,\n"
" Token.leftBrace: Color.gray,\n"
" Token.rightBrace: Color.gray,\n"
" Token.colon: Color.gray,\n"
" Token.dot: Color.gray,\n"
" Token.dotDot: Color.none,\n"
" Token.dotDotDot: Color.none,\n"
" Token.comma: Color.gray,\n"
" Token.star: Color.none,\n"
" Token.slash: Color.none,\n"
" Token.percent: Color.none,\n"
" Token.plus: Color.none,\n"
" Token.minus: Color.none,\n"
" Token.pipe: Color.none,\n"
" Token.pipePipe: Color.none,\n"
" Token.caret: Color.none,\n"
" Token.amp: Color.none,\n"
" Token.ampAmp: Color.none,\n"
" Token.question: Color.none,\n"
" Token.bang: Color.none,\n"
" Token.tilde: Color.none,\n"
" Token.equal: Color.none,\n"
" Token.less: Color.none,\n"
" Token.lessEqual: Color.none,\n"
" Token.lessLess: Color.none,\n"
" Token.greater: Color.none,\n"
" Token.greaterEqual: Color.none,\n"
" Token.greaterGreater: Color.none,\n"
" Token.equalEqual: Color.none,\n"
" Token.bangEqual: Color.none,\n"
"\n"
" // Keywords.\n"
" Token.breakKeyword: Color.cyan,\n"
" Token.classKeyword: Color.cyan,\n"
" Token.constructKeyword: Color.cyan,\n"
" Token.elseKeyword: Color.cyan,\n"
" Token.falseKeyword: Color.cyan,\n"
" Token.forKeyword: Color.cyan,\n"
" Token.foreignKeyword: Color.cyan,\n"
" Token.ifKeyword: Color.cyan,\n"
" Token.importKeyword: Color.cyan,\n"
" Token.inKeyword: Color.cyan,\n"
" Token.isKeyword: Color.cyan,\n"
" Token.nullKeyword: Color.cyan,\n"
" Token.returnKeyword: Color.cyan,\n"
" Token.staticKeyword: Color.cyan,\n"
" Token.superKeyword: Color.cyan,\n"
" Token.thisKeyword: Color.cyan,\n"
" Token.trueKeyword: Color.cyan,\n"
" Token.varKeyword: Color.cyan,\n"
" Token.whileKeyword: Color.cyan,\n"
"\n"
" Token.field: Color.none,\n"
" Token.name: Color.none,\n"
" Token.number: Color.magenta,\n"
" Token.string: Color.yellow,\n"
" Token.interpolation: Color.yellow,\n"
" Token.comment: Color.gray,\n"
" Token.whitespace: Color.none,\n"
" Token.line: Color.none,\n"
" Token.error: Color.red,\n"
" Token.eof: Color.none,\n"
"}\n"
"\n"
"// Data table for tokens that are tokenized using maximal munch.\n"
"//\n"
"// The key is the character that starts the token or tokens. After that is a\n"
"// list of token types and characters. As long as the next character is matched,\n"
"// the type will update to the type after that character.\n"
"var PUNCTUATORS = {\n"
" Chars.leftParen: [Token.leftParen],\n"
" Chars.rightParen: [Token.rightParen],\n"
" Chars.leftBracket: [Token.leftBracket],\n"
" Chars.rightBracket: [Token.rightBracket],\n"
" Chars.leftBrace: [Token.leftBrace],\n"
" Chars.rightBrace: [Token.rightBrace],\n"
" Chars.colon: [Token.colon],\n"
" Chars.comma: [Token.comma],\n"
" Chars.star: [Token.star],\n"
" Chars.percent: [Token.percent],\n"
" Chars.plus: [Token.plus],\n"
" Chars.minus: [Token.minus],\n"
" Chars.tilde: [Token.tilde],\n"
" Chars.caret: [Token.caret],\n"
" Chars.question: [Token.question],\n"
" Chars.lineFeed: [Token.line],\n"
"\n"
" Chars.pipe: [Token.pipe, Chars.pipe, Token.pipePipe],\n"
" Chars.amp: [Token.amp, Chars.amp, Token.ampAmp],\n"
" Chars.bang: [Token.bang, Chars.equal, Token.bangEqual],\n"
" Chars.equal: [Token.equal, Chars.equal, Token.equalEqual],\n"
"\n"
" Chars.dot: [Token.dot, Chars.dot, Token.dotDot, Chars.dot, Token.dotDotDot]\n"
"}\n"
"\n"
"/// Tokenizes a string of input. This lexer differs from most in that it\n"
"/// silently ignores errors from incomplete input, like a string literal with\n"
"/// no closing quote. That's because this is intended to be run on a line of\n"
"/// input while the user is still typing it.\n"
"class Lexer {\n"
" construct new(source) {\n"
" _source = source\n"
"\n"
" // Due to the magic of UTF-8, we can safely treat Wren source as a series\n"
" // of bytes, since the only code points that are meaningful to Wren fit in\n"
" // ASCII. The only place where non-ASCII code points can occur is inside\n"
" // string literals and comments and the lexer safely treats those as opaque\n"
" // bytes.\n"
" _bytes = source.bytes\n"
"\n"
" _start = 0\n"
" _current = 0\n"
"\n"
" // The stack of ongoing interpolated strings. Each element in the list is\n"
" // a single level of interpolation nesting. The value of the element is the\n"
" // number of unbalanced \"(\" still remaining to be closed.\n"
" _interpolations = []\n"
" }\n"
"\n"
" readToken() {\n"
" if (_current >= _bytes.count) return makeToken(Token.eof)\n"
"\n"
" _start = _current\n"
" var c = _bytes[_current]\n"
" advance()\n"
"\n"
" if (!_interpolations.isEmpty) {\n"
" if (c == Chars.leftParen) {\n"
" _interpolations[-1] = _interpolations[-1] + 1\n"
" } else if (c == Chars.rightParen) {\n"
" _interpolations[-1] = _interpolations[-1] - 1\n"
"\n"
" // The last \")\" in an interpolated expression ends the expression and\n"
" // resumes the string.\n"
" if (_interpolations[-1] == 0) {\n"
" // This is the final \")\", so the interpolation expression has ended.\n"
" // This \")\" now begins the next section of the template string.\n"
" _interpolations.removeAt(-1)\n"
" return readString()\n"
" }\n"
" }\n"
" }\n"
"\n"
" if (PUNCTUATORS.containsKey(c)) {\n"
" var punctuator = PUNCTUATORS[c]\n"
" var type = punctuator[0]\n"
" var i = 1\n"
" while (i < punctuator.count) {\n"
" if (!match(punctuator[i])) break\n"
" type = punctuator[i + 1]\n"
" i = i + 2\n"
" }\n"
"\n"
" return makeToken(type)\n"
" }\n"
"\n"
" // Handle \"<\", \"<<\", and \"<=\".\n"
" if (c == Chars.less) {\n"
" if (match(Chars.less)) return makeToken(Token.lessLess)\n"
" if (match(Chars.equal)) return makeToken(Token.lessEqual)\n"
" return makeToken(Token.less)\n"
" }\n"
"\n"
" // Handle \">\", \">>\", and \">=\".\n"
" if (c == Chars.greater) {\n"
" if (match(Chars.greater)) return makeToken(Token.greaterGreater)\n"
" if (match(Chars.equal)) return makeToken(Token.greaterEqual)\n"
" return makeToken(Token.greater)\n"
" }\n"
"\n"
" // Handle \"/\", \"//\", and \"/*\".\n"
" if (c == Chars.slash) {\n"
" if (match(Chars.slash)) return readLineComment()\n"
" if (match(Chars.star)) return readBlockComment()\n"
" return makeToken(Token.slash)\n"
" }\n"
"\n"
" if (c == Chars.underscore) return readField()\n"
" if (c == Chars.quote) return readString()\n"
"\n"
" if (c == Chars.zero && peek() == Chars.lowerX) return readHexNumber()\n"
" if (Chars.isWhitespace(c)) return readWhitespace()\n"
" if (Chars.isDigit(c)) return readNumber()\n"
" if (Chars.isAlpha(c)) return readName()\n"
"\n"
" return makeToken(Token.error)\n"
" }\n"
"\n"
" // Reads a line comment until the end of the line is reached.\n"
" readLineComment() {\n"
" // A line comment stops at the newline since newlines are significant.\n"
" while (peek() != Chars.lineFeed && !isAtEnd) {\n"
" advance()\n"
" }\n"
"\n"
" return makeToken(Token.comment)\n"
" }\n"
"\n"
" readBlockComment() {\n"
" // Block comments can nest.\n"
" var nesting = 1\n"
" while (nesting > 0) {\n"
" // TODO: Report error.\n"
" if (isAtEnd) break\n"
"\n"
" if (peek() == Chars.slash && peek(1) == Chars.star) {\n"
" advance()\n"
" advance()\n"
" nesting = nesting + 1\n"
" } else if (peek() == Chars.star && peek(1) == Chars.slash) {\n"
" advance()\n"
" advance()\n"
" nesting = nesting - 1\n"
" if (nesting == 0) break\n"
" } else {\n"
" advance()\n"
" }\n"
" }\n"
"\n"
" return makeToken(Token.comment)\n"
" }\n"
"\n"
" // Reads a static or instance field.\n"
" readField() {\n"
" var type = Token.field\n"
"\n"
" // Read the rest of the name.\n"
" while (match {|c| Chars.isAlphaNumeric(c) }) {}\n"
"\n"
" return makeToken(type)\n"
" }\n"
"\n"
" // Reads a string literal.\n"
" readString() {\n"
" var type = Token.string\n"
"\n"
" while (!isAtEnd) {\n"
" var c = _bytes[_current]\n"
" advance()\n"
"\n"
" if (c == Chars.backslash) {\n"
" // TODO: Process specific escapes and validate them.\n"
" if (!isAtEnd) advance()\n"
" } else if (c == Chars.percent) {\n"
" // Consume the '('.\n"
" if (!isAtEnd) advance()\n"
" // TODO: Handle missing '('.\n"
" _interpolations.add(1)\n"
" type = Token.interpolation\n"
" break\n"
" } else if (c == Chars.quote) {\n"
" break\n"
" }\n"
" }\n"
"\n"
" return makeToken(type)\n"
" }\n"
"\n"
" // Reads a number literal.\n"
" readHexNumber() {\n"
" // Skip past the `x`.\n"
" advance()\n"
"\n"
" // Read the rest of the number.\n"
" while (match {|c| Chars.isHexDigit(c) }) {}\n"
" return makeToken(Token.number)\n"
" }\n"
"\n"
" // Reads a series of whitespace characters.\n"
" readWhitespace() {\n"
" // Read the rest of the whitespace.\n"
" while (match {|c| Chars.isWhitespace(c) }) {}\n"
"\n"
" return makeToken(Token.whitespace)\n"
" }\n"
"\n"
" // Reads a number literal.\n"
" readNumber() {\n"
" // Read the rest of the number.\n"
" while (match {|c| Chars.isDigit(c) }) {}\n"
"\n"
" // TODO: Floating point, scientific.\n"
" return makeToken(Token.number)\n"
" }\n"
"\n"
" // Reads an identifier or keyword token.\n"
" readName() {\n"
" // Read the rest of the name.\n"
" while (match {|c| Chars.isAlphaNumeric(c) }) {}\n"
"\n"
" var text = _source[_start..._current]\n"
" var type = Token.name\n"
" if (KEYWORDS.containsKey(text)) {\n"
" type = KEYWORDS[text]\n"
" }\n"
"\n"
" return Token.new(_source, type, _start, _current - _start)\n"
" }\n"
"\n"
" // Returns `true` if we have scanned all characters.\n"
" isAtEnd { _current >= _bytes.count }\n"
"\n"
" // Advances past the current character.\n"
" advance() {\n"
" _current = _current + 1\n"
" }\n"
"\n"
" // Returns the byte value of the current character.\n"
" peek() { peek(0) }\n"
"\n"
" // Returns the byte value of the character [n] bytes past the current\n"
" // character.\n"
" peek(n) {\n"
" if (_current + n >= _bytes.count) return -1\n"
" return _bytes[_current + n]\n"
" }\n"
"\n"
" // Consumes the current character if it matches [condition], which can be a\n"
" // numeric code point value or a function that takes a code point and returns\n"
" // `true` if the code point matches.\n"
" match(condition) {\n"
" if (isAtEnd) return false\n"
"\n"
" var c = _bytes[_current]\n"
" if (condition is Fn) {\n"
" if (!condition.call(c)) return false\n"
" } else if (c != condition) {\n"
" return false\n"
" }\n"
"\n"
" advance()\n"
" return true\n"
" }\n"
"\n"
" // Creates a token of [type] from the current character range.\n"
" makeToken(type) { Token.new(_source, type, _start, _current - _start) }\n"
"}\n"
"\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";