Execute input in the REPL.

Guesses whether the input is an expression or statement and handles it
appropriately. Finally, after over a year, the Wren REPL automatically
prints "3" if you type in "1 + 2". \o/
This commit is contained in:
Bob Nystrom
2016-06-09 19:16:41 -07:00
parent 689cd42269
commit bc7f1de758
9 changed files with 347 additions and 177 deletions
+145 -78
View File
@@ -1,3 +1,4 @@
import "meta" for Meta
import "io" for Stdin
class EscapeBracket {
@@ -38,7 +39,7 @@ class Repl {
} else if (byte == Chars.escape) {
handleEscape()
} else if (byte == Chars.carriageReturn) {
executeLine()
executeInput()
} else if (byte == Chars.delete) {
deleteLeft()
} else if (byte >= Chars.space && byte <= Chars.tilde) {
@@ -100,11 +101,73 @@ class Repl {
}
}
executeLine() {
// TODO: Execute line.
executeInput() {
System.print()
var input = _line
_line = ""
_cursor = 0
System.print()
// Guess if it looks like a statement or expression. Statements need to be
// evaluated at the top level in case they declare variables, but they
// don't return a value. Expressions need to have their result displayed.
var tokens = lex(input, false)
if (tokens.isEmpty) {
// No code, so do nothing.
// TODO: Temp.
System.print("empty")
return
}
var first = tokens[0]
var isStatement =
first.type == Token.breakKeyword ||
first.type == Token.classKeyword ||
first.type == Token.forKeyword ||
first.type == Token.foreignKeyword ||
first.type == Token.ifKeyword ||
first.type == Token.importKeyword ||
first.type == Token.returnKeyword ||
first.type == Token.varKeyword ||
first.type == Token.whileKeyword
var fiber
if (isStatement) {
fiber = Fiber.new {
Meta.eval(input)
}
var result = fiber.try()
if (fiber.error == null) return
} else {
var function = Meta.compileExpression(input)
if (function == null) return
fiber = Fiber.new(function)
var result = fiber.try()
if (fiber.error == null) {
// TODO: Handle error in result.toString.
System.print("%(Color.brightWhite)%(result)%(Color.none)")
return
}
System.print("%(Color.red)Runtime error: %(result)%(Color.none)")
// TODO: Print entire stack.
}
lex(line, includeWhitespace) {
var lexer = Lexer.new(line)
var tokens = []
while (true) {
var token = lexer.readToken()
if (token.type == Token.eof) break
if (includeWhitespace ||
(token.type != Token.comment && token.type != Token.whitespace)) {
tokens.add(token)
}
}
return tokens
}
refreshLine() {
@@ -113,14 +176,11 @@ class Repl {
// Show the prompt at the beginning of the line.
System.write(Color.gray)
System.write("\r>> ")
System.write("\r> ")
System.write(Color.none)
// Syntax highlight the line.
var lexer = Lexer.new(_line)
while (true) {
var token = lexer.readToken()
for (token in lex(_line, true)) {
if (token.type == Token.eof) break
System.write(TOKEN_COLORS[token.type])
@@ -129,7 +189,7 @@ class Repl {
}
// Position the cursor.
System.write("\r\x1b[%(3 + _cursor)C")
System.write("\r\x1b[%(2 + _cursor)C")
}
}
@@ -146,6 +206,8 @@ class Color {
static white { "\x1b[37m" }
static gray { "\x1b[30;1m" }
static pink { "\x1b[31;1m" }
static brightWhite { "\x1b[37;1m" }
}
/// Utilities for working with characters.
@@ -329,6 +391,74 @@ var KEYWORDS = {
"while": Token.whileKeyword
}
var TOKEN_COLORS = {
Token.leftParen: Color.gray,
Token.rightParen: Color.gray,
Token.leftBracket: Color.gray,
Token.rightBracket: Color.gray,
Token.leftBrace: Color.gray,
Token.rightBrace: Color.gray,
Token.colon: Color.gray,
Token.dot: Color.gray,
Token.dotDot: Color.none,
Token.dotDotDot: Color.none,
Token.comma: Color.gray,
Token.star: Color.none,
Token.slash: Color.none,
Token.percent: Color.none,
Token.plus: Color.none,
Token.minus: Color.none,
Token.pipe: Color.none,
Token.pipePipe: Color.none,
Token.caret: Color.none,
Token.amp: Color.none,
Token.ampAmp: Color.none,
Token.question: Color.none,
Token.bang: Color.none,
Token.tilde: Color.none,
Token.equal: Color.none,
Token.less: Color.none,
Token.lessEqual: Color.none,
Token.lessLess: Color.none,
Token.greater: Color.none,
Token.greaterEqual: Color.none,
Token.greaterGreater: Color.none,
Token.equalEqual: Color.none,
Token.bangEqual: Color.none,
// Keywords.
Token.breakKeyword: Color.cyan,
Token.classKeyword: Color.cyan,
Token.constructKeyword: Color.cyan,
Token.elseKeyword: Color.cyan,
Token.falseKeyword: Color.cyan,
Token.forKeyword: Color.cyan,
Token.foreignKeyword: Color.cyan,
Token.ifKeyword: Color.cyan,
Token.importKeyword: Color.cyan,
Token.inKeyword: Color.cyan,
Token.isKeyword: Color.cyan,
Token.nullKeyword: Color.cyan,
Token.returnKeyword: Color.cyan,
Token.staticKeyword: Color.cyan,
Token.superKeyword: Color.cyan,
Token.thisKeyword: Color.cyan,
Token.trueKeyword: Color.cyan,
Token.varKeyword: Color.cyan,
Token.whileKeyword: Color.cyan,
Token.field: Color.none,
Token.name: Color.none,
Token.number: Color.magenta,
Token.string: Color.yellow,
Token.interpolation: Color.yellow,
Token.comment: Color.gray,
Token.whitespace: Color.none,
Token.line: Color.none,
Token.error: Color.red,
Token.eof: Color.none,
}
// Data table for tokens that are tokenized using maximal munch.
//
// The key is the character that starts the token or tokens. After that is a
@@ -360,6 +490,10 @@ var PUNCTUATORS = {
Chars.dot: [Token.dot, Chars.dot, Token.dotDot, Chars.dot, Token.dotDotDot]
}
/// Tokenizes a string of input. This lexer differs from most in that it
/// silently ignores errors from incomplete input, like a string literal with
/// no closing quote. That's because this is intended to be run on a line of
/// input while the user is still typing it.
class Lexer {
construct new(source) {
_source = source
@@ -599,72 +733,5 @@ class Lexer {
makeToken(type) { Token.new(_source, type, _start, _current - _start) }
}
var TOKEN_COLORS = {
Token.leftParen: Color.gray,
Token.rightParen: Color.gray,
Token.leftBracket: Color.gray,
Token.rightBracket: Color.gray,
Token.leftBrace: Color.gray,
Token.rightBrace: Color.gray,
Token.colon: Color.gray,
Token.dot: Color.gray,
Token.dotDot: Color.none,
Token.dotDotDot: Color.none,
Token.comma: Color.gray,
Token.star: Color.none,
Token.slash: Color.none,
Token.percent: Color.none,
Token.plus: Color.none,
Token.minus: Color.none,
Token.pipe: Color.none,
Token.pipePipe: Color.none,
Token.caret: Color.none,
Token.amp: Color.none,
Token.ampAmp: Color.none,
Token.question: Color.none,
Token.bang: Color.none,
Token.tilde: Color.none,
Token.equal: Color.none,
Token.less: Color.none,
Token.lessEqual: Color.none,
Token.lessLess: Color.none,
Token.greater: Color.none,
Token.greaterEqual: Color.none,
Token.greaterGreater: Color.none,
Token.equalEqual: Color.none,
Token.bangEqual: Color.none,
// Keywords.
Token.breakKeyword: Color.cyan,
Token.classKeyword: Color.cyan,
Token.constructKeyword: Color.cyan,
Token.elseKeyword: Color.cyan,
Token.falseKeyword: Color.cyan,
Token.forKeyword: Color.cyan,
Token.foreignKeyword: Color.cyan,
Token.ifKeyword: Color.cyan,
Token.importKeyword: Color.cyan,
Token.inKeyword: Color.cyan,
Token.isKeyword: Color.cyan,
Token.nullKeyword: Color.cyan,
Token.returnKeyword: Color.cyan,
Token.staticKeyword: Color.cyan,
Token.superKeyword: Color.cyan,
Token.thisKeyword: Color.cyan,
Token.trueKeyword: Color.cyan,
Token.varKeyword: Color.cyan,
Token.whileKeyword: Color.cyan,
Token.field: Color.none,
Token.name: Color.none,
Token.number: Color.magenta,
Token.string: Color.yellow,
Token.interpolation: Color.yellow,
Token.comment: Color.gray,
Token.whitespace: Color.none,
Token.line: Color.none,
Token.error: Color.red,
Token.eof: Color.none,
}
// Fire up the REPL.
Repl.new().run()
+147 -78
View File
@@ -1,5 +1,6 @@
// Generated automatically from src/module/repl.wren. Do not edit.
static const char* replModuleSource =
"import \"meta\" for Meta\n"
"import \"io\" for Stdin\n"
"\n"
"class EscapeBracket {\n"
@@ -40,7 +41,7 @@ static const char* replModuleSource =
" } else if (byte == Chars.escape) {\n"
" handleEscape()\n"
" } else if (byte == Chars.carriageReturn) {\n"
" executeLine()\n"
" executeInput()\n"
" } else if (byte == Chars.delete) {\n"
" deleteLeft()\n"
" } else if (byte >= Chars.space && byte <= Chars.tilde) {\n"
@@ -102,11 +103,75 @@ static const char* replModuleSource =
" }\n"
" }\n"
"\n"
" executeLine() {\n"
" // TODO: Execute line.\n"
" executeInput() {\n"
" System.print()\n"
" var input = _line\n"
" _line = \"\"\n"
" _cursor = 0\n"
" System.print()\n"
"\n"
" // Guess if it looks like a statement or expression. Statements need to be\n"
" // evaluated at the top level in case they declare variables, but they\n"
" // don't return a value. Expressions need to have their result displayed.\n"
" var tokens = lex(input, false)\n"
" if (tokens.isEmpty) {\n"
" // No code, so do nothing.\n"
" // TODO: Temp.\n"
" System.print(\"empty\")\n"
" return\n"
" }\n"
"\n"
" var first = tokens[0]\n"
" var isStatement =\n"
" first.type == Token.breakKeyword ||\n"
" first.type == Token.classKeyword ||\n"
" first.type == Token.forKeyword ||\n"
" first.type == Token.foreignKeyword ||\n"
" first.type == Token.ifKeyword ||\n"
" first.type == Token.importKeyword ||\n"
" first.type == Token.returnKeyword ||\n"
" first.type == Token.varKeyword ||\n"
" first.type == Token.whileKeyword\n"
"\n"
" if (isStatement) {\n"
" var fiber = Fiber.new {\n"
" Meta.eval(input)\n"
" }\n"
"\n"
" var result = fiber.try()\n"
" if (fiber.error != null) {\n"
" System.print(\"%(Color.red)Runtime error: %(result)%(Color.none)\")\n"
" // TODO: Print entire stack.\n"
" }\n"
" } else {\n"
" var function = Meta.compileExpression(input)\n"
" if (function == null) return\n"
"\n"
" var fiber = Fiber.new(function)\n"
" var result = fiber.try()\n"
" if (fiber.error == null) {\n"
" // TODO: Handle error in result.toString.\n"
" System.print(\"%(Color.brightWhite)%(result)%(Color.none)\")\n"
" } else {\n"
" System.print(\"%(Color.red)Runtime error: %(result)%(Color.none)\")\n"
" // TODO: Print entire stack.\n"
" }\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"
" refreshLine() {\n"
@@ -115,14 +180,11 @@ static const char* replModuleSource =
"\n"
" // Show the prompt at the beginning of the line.\n"
" System.write(Color.gray)\n"
" System.write(\"\r>> \")\n"
" System.write(\"\r> \")\n"
" System.write(Color.none)\n"
"\n"
" // Syntax highlight the line.\n"
" var lexer = Lexer.new(_line)\n"
"\n"
" while (true) {\n"
" var token = lexer.readToken()\n"
" for (token in lex(_line, true)) {\n"
" if (token.type == Token.eof) break\n"
"\n"
" System.write(TOKEN_COLORS[token.type])\n"
@@ -131,7 +193,7 @@ static const char* replModuleSource =
" }\n"
"\n"
" // Position the cursor.\n"
" System.write(\"\r\x1b[%(3 + _cursor)C\")\n"
" System.write(\"\r\x1b[%(2 + _cursor)C\")\n"
" }\n"
"}\n"
"\n"
@@ -148,6 +210,8 @@ static const char* replModuleSource =
" 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"
@@ -331,6 +395,74 @@ static const char* replModuleSource =
" \"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"
@@ -362,6 +494,10 @@ static const char* replModuleSource =
" 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"
@@ -601,72 +737,5 @@ static const char* replModuleSource =
" makeToken(type) { Token.new(_source, type, _start, _current - _start) }\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"
"// Fire up the REPL.\n"
"Repl.new().run()\n";