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:
parent
689cd42269
commit
bc7f1de758
@ -1,3 +1,4 @@
|
|||||||
|
import "meta" for Meta
|
||||||
import "io" for Stdin
|
import "io" for Stdin
|
||||||
|
|
||||||
class EscapeBracket {
|
class EscapeBracket {
|
||||||
@ -38,7 +39,7 @@ class Repl {
|
|||||||
} else if (byte == Chars.escape) {
|
} else if (byte == Chars.escape) {
|
||||||
handleEscape()
|
handleEscape()
|
||||||
} else if (byte == Chars.carriageReturn) {
|
} else if (byte == Chars.carriageReturn) {
|
||||||
executeLine()
|
executeInput()
|
||||||
} else if (byte == Chars.delete) {
|
} else if (byte == Chars.delete) {
|
||||||
deleteLeft()
|
deleteLeft()
|
||||||
} else if (byte >= Chars.space && byte <= Chars.tilde) {
|
} else if (byte >= Chars.space && byte <= Chars.tilde) {
|
||||||
@ -100,11 +101,73 @@ class Repl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
executeLine() {
|
executeInput() {
|
||||||
// TODO: Execute line.
|
System.print()
|
||||||
|
var input = _line
|
||||||
_line = ""
|
_line = ""
|
||||||
_cursor = 0
|
_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() {
|
refreshLine() {
|
||||||
@ -113,14 +176,11 @@ class Repl {
|
|||||||
|
|
||||||
// Show the prompt at the beginning of the line.
|
// Show the prompt at the beginning of the line.
|
||||||
System.write(Color.gray)
|
System.write(Color.gray)
|
||||||
System.write("\r>> ")
|
System.write("\r> ")
|
||||||
System.write(Color.none)
|
System.write(Color.none)
|
||||||
|
|
||||||
// Syntax highlight the line.
|
// Syntax highlight the line.
|
||||||
var lexer = Lexer.new(_line)
|
for (token in lex(_line, true)) {
|
||||||
|
|
||||||
while (true) {
|
|
||||||
var token = lexer.readToken()
|
|
||||||
if (token.type == Token.eof) break
|
if (token.type == Token.eof) break
|
||||||
|
|
||||||
System.write(TOKEN_COLORS[token.type])
|
System.write(TOKEN_COLORS[token.type])
|
||||||
@ -129,7 +189,7 @@ class Repl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Position the cursor.
|
// 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 white { "\x1b[37m" }
|
||||||
|
|
||||||
static gray { "\x1b[30;1m" }
|
static gray { "\x1b[30;1m" }
|
||||||
|
static pink { "\x1b[31;1m" }
|
||||||
|
static brightWhite { "\x1b[37;1m" }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Utilities for working with characters.
|
/// Utilities for working with characters.
|
||||||
@ -329,6 +391,74 @@ var KEYWORDS = {
|
|||||||
"while": Token.whileKeyword
|
"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.
|
// 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
|
// 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]
|
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 {
|
class Lexer {
|
||||||
construct new(source) {
|
construct new(source) {
|
||||||
_source = source
|
_source = source
|
||||||
@ -599,72 +733,5 @@ class Lexer {
|
|||||||
makeToken(type) { Token.new(_source, type, _start, _current - _start) }
|
makeToken(type) { Token.new(_source, type, _start, _current - _start) }
|
||||||
}
|
}
|
||||||
|
|
||||||
var TOKEN_COLORS = {
|
// Fire up the REPL.
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
Repl.new().run()
|
Repl.new().run()
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
// Generated automatically from src/module/repl.wren. Do not edit.
|
// Generated automatically from src/module/repl.wren. Do not edit.
|
||||||
static const char* replModuleSource =
|
static const char* replModuleSource =
|
||||||
|
"import \"meta\" for Meta\n"
|
||||||
"import \"io\" for Stdin\n"
|
"import \"io\" for Stdin\n"
|
||||||
"\n"
|
"\n"
|
||||||
"class EscapeBracket {\n"
|
"class EscapeBracket {\n"
|
||||||
@ -40,7 +41,7 @@ static const char* replModuleSource =
|
|||||||
" } else if (byte == Chars.escape) {\n"
|
" } else if (byte == Chars.escape) {\n"
|
||||||
" handleEscape()\n"
|
" handleEscape()\n"
|
||||||
" } else if (byte == Chars.carriageReturn) {\n"
|
" } else if (byte == Chars.carriageReturn) {\n"
|
||||||
" executeLine()\n"
|
" executeInput()\n"
|
||||||
" } else if (byte == Chars.delete) {\n"
|
" } else if (byte == Chars.delete) {\n"
|
||||||
" deleteLeft()\n"
|
" deleteLeft()\n"
|
||||||
" } else if (byte >= Chars.space && byte <= Chars.tilde) {\n"
|
" } else if (byte >= Chars.space && byte <= Chars.tilde) {\n"
|
||||||
@ -102,11 +103,75 @@ static const char* replModuleSource =
|
|||||||
" }\n"
|
" }\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
"\n"
|
"\n"
|
||||||
" executeLine() {\n"
|
" executeInput() {\n"
|
||||||
" // TODO: Execute line.\n"
|
" System.print()\n"
|
||||||
|
" var input = _line\n"
|
||||||
" _line = \"\"\n"
|
" _line = \"\"\n"
|
||||||
" _cursor = 0\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"
|
||||||
"\n"
|
"\n"
|
||||||
" refreshLine() {\n"
|
" refreshLine() {\n"
|
||||||
@ -115,14 +180,11 @@ static const char* replModuleSource =
|
|||||||
"\n"
|
"\n"
|
||||||
" // Show the prompt at the beginning of the line.\n"
|
" // Show the prompt at the beginning of the line.\n"
|
||||||
" System.write(Color.gray)\n"
|
" System.write(Color.gray)\n"
|
||||||
" System.write(\"\r>> \")\n"
|
" System.write(\"\r> \")\n"
|
||||||
" System.write(Color.none)\n"
|
" System.write(Color.none)\n"
|
||||||
"\n"
|
"\n"
|
||||||
" // Syntax highlight the line.\n"
|
" // Syntax highlight the line.\n"
|
||||||
" var lexer = Lexer.new(_line)\n"
|
" for (token in lex(_line, true)) {\n"
|
||||||
"\n"
|
|
||||||
" while (true) {\n"
|
|
||||||
" var token = lexer.readToken()\n"
|
|
||||||
" if (token.type == Token.eof) break\n"
|
" if (token.type == Token.eof) break\n"
|
||||||
"\n"
|
"\n"
|
||||||
" System.write(TOKEN_COLORS[token.type])\n"
|
" System.write(TOKEN_COLORS[token.type])\n"
|
||||||
@ -131,7 +193,7 @@ static const char* replModuleSource =
|
|||||||
" }\n"
|
" }\n"
|
||||||
"\n"
|
"\n"
|
||||||
" // Position the cursor.\n"
|
" // Position the cursor.\n"
|
||||||
" System.write(\"\r\x1b[%(3 + _cursor)C\")\n"
|
" System.write(\"\r\x1b[%(2 + _cursor)C\")\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
"}\n"
|
"}\n"
|
||||||
"\n"
|
"\n"
|
||||||
@ -148,6 +210,8 @@ static const char* replModuleSource =
|
|||||||
" static white { \"\x1b[37m\" }\n"
|
" static white { \"\x1b[37m\" }\n"
|
||||||
"\n"
|
"\n"
|
||||||
" static gray { \"\x1b[30;1m\" }\n"
|
" static gray { \"\x1b[30;1m\" }\n"
|
||||||
|
" static pink { \"\x1b[31;1m\" }\n"
|
||||||
|
" static brightWhite { \"\x1b[37;1m\" }\n"
|
||||||
"}\n"
|
"}\n"
|
||||||
"\n"
|
"\n"
|
||||||
"/// Utilities for working with characters.\n"
|
"/// Utilities for working with characters.\n"
|
||||||
@ -331,6 +395,74 @@ static const char* replModuleSource =
|
|||||||
" \"while\": Token.whileKeyword\n"
|
" \"while\": Token.whileKeyword\n"
|
||||||
"}\n"
|
"}\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"
|
"// Data table for tokens that are tokenized using maximal munch.\n"
|
||||||
"//\n"
|
"//\n"
|
||||||
"// The key is the character that starts the token or tokens. After that is a\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"
|
" Chars.dot: [Token.dot, Chars.dot, Token.dotDot, Chars.dot, Token.dotDotDot]\n"
|
||||||
"}\n"
|
"}\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"
|
"class Lexer {\n"
|
||||||
" construct new(source) {\n"
|
" construct new(source) {\n"
|
||||||
" _source = source\n"
|
" _source = source\n"
|
||||||
@ -601,72 +737,5 @@ static const char* replModuleSource =
|
|||||||
" makeToken(type) { Token.new(_source, type, _start, _current - _start) }\n"
|
" makeToken(type) { Token.new(_source, type, _start, _current - _start) }\n"
|
||||||
"}\n"
|
"}\n"
|
||||||
"\n"
|
"\n"
|
||||||
"var TOKEN_COLORS = {\n"
|
"// Fire up the REPL.\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"
|
|
||||||
"Repl.new().run()\n";
|
"Repl.new().run()\n";
|
||||||
|
|||||||
@ -15,8 +15,12 @@ void metaCompile(WrenVM* vm)
|
|||||||
ObjClosure* caller = vm->fiber->frames[vm->fiber->numFrames - 2].closure;
|
ObjClosure* caller = vm->fiber->frames[vm->fiber->numFrames - 2].closure;
|
||||||
ObjModule* module = caller->fn->module;
|
ObjModule* module = caller->fn->module;
|
||||||
|
|
||||||
|
const char* source = wrenGetSlotString(vm, 1);
|
||||||
|
bool isExpression = wrenGetSlotBool(vm, 2);
|
||||||
|
bool printErrors = wrenGetSlotBool(vm, 3);
|
||||||
|
|
||||||
// Compile it.
|
// Compile it.
|
||||||
ObjFn* fn = wrenCompile(vm, module, wrenGetSlotString(vm, 1), false);
|
ObjFn* fn = wrenCompile(vm, module, source, isExpression, printErrors);
|
||||||
|
|
||||||
// Return the result. We can't use the public API for this since we have a
|
// Return the result. We can't use the public API for this since we have a
|
||||||
// bare ObjFn.
|
// bare ObjFn.
|
||||||
@ -45,7 +49,7 @@ WrenForeignMethodFn wrenMetaBindForeignMethod(WrenVM* vm,
|
|||||||
// There is only one foreign method in the meta module.
|
// There is only one foreign method in the meta module.
|
||||||
ASSERT(strcmp(className, "Meta") == 0, "Should be in Meta class.");
|
ASSERT(strcmp(className, "Meta") == 0, "Should be in Meta class.");
|
||||||
ASSERT(isStatic, "Should be static.");
|
ASSERT(isStatic, "Should be static.");
|
||||||
ASSERT(strcmp(signature, "compile_(_)") == 0, "Should be compile method.");
|
ASSERT(strcmp(signature, "compile_(_,_,_)") == 0, "Should be compile method.");
|
||||||
|
|
||||||
return metaCompile;
|
return metaCompile;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,12 +2,17 @@ class Meta {
|
|||||||
static eval(source) {
|
static eval(source) {
|
||||||
if (!(source is String)) Fiber.abort("Source code must be a string.")
|
if (!(source is String)) Fiber.abort("Source code must be a string.")
|
||||||
|
|
||||||
var fn = compile_(source)
|
var fn = compile_(source, false, false)
|
||||||
// TODO: Include compile errors.
|
// TODO: Include compile errors.
|
||||||
if (fn == null) Fiber.abort("Could not compile source code.")
|
if (fn == null) Fiber.abort("Could not compile source code.")
|
||||||
|
|
||||||
Fiber.new(fn).call()
|
Fiber.new(fn).call()
|
||||||
}
|
}
|
||||||
|
|
||||||
foreign static compile_(source)
|
static compileExpression(source) {
|
||||||
|
if (!(source is String)) Fiber.abort("Source code must be a string.")
|
||||||
|
return compile_(source, true, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
foreign static compile_(source, isExpression, printErrors)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,12 +4,17 @@ static const char* metaModuleSource =
|
|||||||
" static eval(source) {\n"
|
" static eval(source) {\n"
|
||||||
" if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n"
|
" if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n"
|
||||||
"\n"
|
"\n"
|
||||||
" var fn = compile_(source)\n"
|
" var fn = compile_(source, false, false)\n"
|
||||||
" // TODO: Include compile errors.\n"
|
" // TODO: Include compile errors.\n"
|
||||||
" if (fn == null) Fiber.abort(\"Could not compile source code.\")\n"
|
" if (fn == null) Fiber.abort(\"Could not compile source code.\")\n"
|
||||||
"\n"
|
"\n"
|
||||||
" Fiber.new(fn).call()\n"
|
" Fiber.new(fn).call()\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
"\n"
|
"\n"
|
||||||
" foreign static compile_(source)\n"
|
" static compileExpression(source) {\n"
|
||||||
|
" if (!(source is String)) Fiber.abort(\"Source code must be a string.\")\n"
|
||||||
|
" return compile_(source, true, true)\n"
|
||||||
|
" }\n"
|
||||||
|
"\n"
|
||||||
|
" foreign static compile_(source, isExpression, printErrors)\n"
|
||||||
"}\n";
|
"}\n";
|
||||||
|
|||||||
@ -3345,6 +3345,7 @@ static void variableDefinition(Compiler* compiler)
|
|||||||
// Compile the initializer.
|
// Compile the initializer.
|
||||||
if (match(compiler, TOKEN_EQ))
|
if (match(compiler, TOKEN_EQ))
|
||||||
{
|
{
|
||||||
|
ignoreNewlines(compiler);
|
||||||
expression(compiler);
|
expression(compiler);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -3387,7 +3388,7 @@ void definition(Compiler* compiler)
|
|||||||
}
|
}
|
||||||
|
|
||||||
ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source,
|
ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source,
|
||||||
bool printErrors)
|
bool isExpression, bool printErrors)
|
||||||
{
|
{
|
||||||
Parser parser;
|
Parser parser;
|
||||||
parser.vm = vm;
|
parser.vm = vm;
|
||||||
@ -3415,29 +3416,39 @@ ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source,
|
|||||||
// Read the first token.
|
// Read the first token.
|
||||||
nextToken(&parser);
|
nextToken(&parser);
|
||||||
|
|
||||||
|
int numExistingVariables = module->variables.count;
|
||||||
|
|
||||||
Compiler compiler;
|
Compiler compiler;
|
||||||
initCompiler(&compiler, &parser, NULL, true);
|
initCompiler(&compiler, &parser, NULL, true);
|
||||||
ignoreNewlines(&compiler);
|
ignoreNewlines(&compiler);
|
||||||
|
|
||||||
while (!match(&compiler, TOKEN_EOF))
|
if (isExpression)
|
||||||
{
|
{
|
||||||
definition(&compiler);
|
expression(&compiler);
|
||||||
|
|
||||||
// If there is no newline, it must be the end of the block on the same line.
|
|
||||||
if (!matchLine(&compiler))
|
|
||||||
{
|
|
||||||
consume(&compiler, TOKEN_EOF, "Expect end of file.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
emitOp(&compiler, CODE_NULL);
|
{
|
||||||
|
while (!match(&compiler, TOKEN_EOF))
|
||||||
|
{
|
||||||
|
definition(&compiler);
|
||||||
|
|
||||||
|
// If there is no newline, it must be the end of the block on the same line.
|
||||||
|
if (!matchLine(&compiler))
|
||||||
|
{
|
||||||
|
consume(&compiler, TOKEN_EOF, "Expect end of file.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emitOp(&compiler, CODE_NULL);
|
||||||
|
}
|
||||||
|
|
||||||
emitOp(&compiler, CODE_RETURN);
|
emitOp(&compiler, CODE_RETURN);
|
||||||
|
|
||||||
// See if there are any implicitly declared module-level variables that never
|
// See if there are any implicitly declared module-level variables that never
|
||||||
// got an explicit definition. They will have values that are numbers
|
// got an explicit definition. They will have values that are numbers
|
||||||
// indicating the line where the variable was first used.
|
// indicating the line where the variable was first used.
|
||||||
for (int i = 0; i < parser.module->variables.count; i++)
|
for (int i = numExistingVariables; i < parser.module->variables.count; i++)
|
||||||
{
|
{
|
||||||
if (IS_NUM(parser.module->variables.data[i]))
|
if (IS_NUM(parser.module->variables.data[i]))
|
||||||
{
|
{
|
||||||
|
|||||||
@ -26,10 +26,14 @@ typedef struct sCompiler Compiler;
|
|||||||
// [ObjFn] that will execute that code when invoked. Returns `NULL` if the
|
// [ObjFn] that will execute that code when invoked. Returns `NULL` if the
|
||||||
// source contains any syntax errors.
|
// source contains any syntax errors.
|
||||||
//
|
//
|
||||||
|
// If [isExpression] is `true`, [source] should be a single expression, and
|
||||||
|
// this compiles it to a function that evaluates and returns that expression.
|
||||||
|
// Otherwise, [source] should be a series of top level statements.
|
||||||
|
//
|
||||||
// If [printErrors] is `true`, any compile errors are output to stderr.
|
// If [printErrors] is `true`, any compile errors are output to stderr.
|
||||||
// Otherwise, they are silently discarded.
|
// Otherwise, they are silently discarded.
|
||||||
ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source,
|
ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source,
|
||||||
bool printErrors);
|
bool isExpression, bool printErrors);
|
||||||
|
|
||||||
// When a class is defined, its superclass is not known until runtime since
|
// When a class is defined, its superclass is not known until runtime since
|
||||||
// class definitions are just imperative statements. Most of the bytecode for a
|
// class definitions are just imperative statements. Most of the bytecode for a
|
||||||
|
|||||||
@ -484,7 +484,7 @@ static ObjFiber* loadModule(WrenVM* vm, Value name, const char* source)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjFn* fn = wrenCompile(vm, module, source, true);
|
ObjFn* fn = wrenCompile(vm, module, source, false, true);
|
||||||
if (fn == NULL)
|
if (fn == NULL)
|
||||||
{
|
{
|
||||||
// TODO: Should we still store the module even if it didn't compile?
|
// TODO: Should we still store the module even if it didn't compile?
|
||||||
|
|||||||
5
test/language/variable/newline_after_equals.wren
Normal file
5
test/language/variable/newline_after_equals.wren
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
var foo =
|
||||||
|
|
||||||
|
|
||||||
|
123
|
||||||
|
System.print(foo) // expect: 123
|
||||||
Loading…
Reference in New Issue
Block a user