mirror of
https://github.com/wren-lang/wren.git
synced 2026-01-11 06:08:41 +01:00
Tab-completion in REPL based on module variables.
This commit is contained in:
@ -1,13 +1,6 @@
|
|||||||
import "meta" for Meta
|
import "meta" for Meta
|
||||||
import "io" for Stdin
|
import "io" for Stdin
|
||||||
|
|
||||||
class EscapeBracket {
|
|
||||||
static up { 65 }
|
|
||||||
static down { 66 }
|
|
||||||
static right { 67 }
|
|
||||||
static left { 68 }
|
|
||||||
}
|
|
||||||
|
|
||||||
class Repl {
|
class Repl {
|
||||||
construct new() {
|
construct new() {
|
||||||
_cursor = 0
|
_cursor = 0
|
||||||
@ -19,7 +12,7 @@ class Repl {
|
|||||||
|
|
||||||
run() {
|
run() {
|
||||||
Stdin.isRaw = true
|
Stdin.isRaw = true
|
||||||
refreshLine()
|
refreshLine(false)
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
var byte = Stdin.readByte()
|
var byte = Stdin.readByte()
|
||||||
@ -32,7 +25,7 @@ class Repl {
|
|||||||
return
|
return
|
||||||
} else if (byte == Chars.ctrlD) {
|
} else if (byte == Chars.ctrlD) {
|
||||||
// If the line is empty, Ctrl_D exits.
|
// If the line is empty, Ctrl_D exits.
|
||||||
if (!_line.isEmpty) {
|
if (_line.isEmpty) {
|
||||||
System.print()
|
System.print()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -43,6 +36,12 @@ class Repl {
|
|||||||
_cursor = _line.count
|
_cursor = _line.count
|
||||||
} else if (byte == Chars.ctrlF) {
|
} else if (byte == Chars.ctrlF) {
|
||||||
cursorRight()
|
cursorRight()
|
||||||
|
} else if (byte == Chars.tab) {
|
||||||
|
var completion = getCompletion()
|
||||||
|
if (completion != null) {
|
||||||
|
_line = _line + completion
|
||||||
|
_cursor = _line.count
|
||||||
|
}
|
||||||
} else if (byte == Chars.ctrlK) {
|
} else if (byte == Chars.ctrlK) {
|
||||||
// Delete everything after the cursor.
|
// Delete everything after the cursor.
|
||||||
_line = _line[0..._cursor]
|
_line = _line[0..._cursor]
|
||||||
@ -77,7 +76,7 @@ class Repl {
|
|||||||
System.print("Unhandled byte: %(byte)")
|
System.print("Unhandled byte: %(byte)")
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshLine()
|
refreshLine(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,6 +156,9 @@ class Repl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
executeInput() {
|
executeInput() {
|
||||||
|
// Remove the completion hint.
|
||||||
|
refreshLine(false)
|
||||||
|
|
||||||
// Add it to the history.
|
// Add it to the history.
|
||||||
_history.add(_line)
|
_history.add(_line)
|
||||||
_historyIndex = _history.count
|
_historyIndex = _history.count
|
||||||
@ -191,12 +193,14 @@ class Repl {
|
|||||||
var fiber
|
var fiber
|
||||||
if (isStatement) {
|
if (isStatement) {
|
||||||
fiber = Fiber.new {
|
fiber = Fiber.new {
|
||||||
|
// TODO: Should evaluate in main module, not repl's own.
|
||||||
Meta.eval(input)
|
Meta.eval(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = fiber.try()
|
var result = fiber.try()
|
||||||
if (fiber.error == null) return
|
if (fiber.error == null) return
|
||||||
} else {
|
} else {
|
||||||
|
// TODO: Should evaluate in main module, not repl's own.
|
||||||
var function = Meta.compileExpression(input)
|
var function = Meta.compileExpression(input)
|
||||||
if (function == null) return
|
if (function == null) return
|
||||||
|
|
||||||
@ -204,6 +208,9 @@ class Repl {
|
|||||||
var result = fiber.try()
|
var result = fiber.try()
|
||||||
if (fiber.error == null) {
|
if (fiber.error == null) {
|
||||||
// TODO: Handle error in result.toString.
|
// 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)")
|
System.print("%(Color.brightWhite)%(result)%(Color.none)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -229,7 +236,7 @@ class Repl {
|
|||||||
return tokens
|
return tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshLine() {
|
refreshLine(showCompletion) {
|
||||||
// Erase the whole line.
|
// Erase the whole line.
|
||||||
System.write("\x1b[2K")
|
System.write("\x1b[2K")
|
||||||
|
|
||||||
@ -247,9 +254,32 @@ class Repl {
|
|||||||
System.write(Color.none)
|
System.write(Color.none)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showCompletion) {
|
||||||
|
var completion = getCompletion()
|
||||||
|
if (completion != null) {
|
||||||
|
System.write("%(Color.gray)%(completion)%(Color.none)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Position the cursor.
|
// 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
|
||||||
|
|
||||||
|
// 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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ANSI color escape sequences.
|
/// ANSI color escape sequences.
|
||||||
@ -351,6 +381,13 @@ class Chars {
|
|||||||
static isWhitespace(c) { c == space || c == tab || c == carriageReturn }
|
static isWhitespace(c) { c == space || c == tab || c == carriageReturn }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EscapeBracket {
|
||||||
|
static up { 0x41 }
|
||||||
|
static down { 0x42 }
|
||||||
|
static right { 0x43 }
|
||||||
|
static left { 0x44 }
|
||||||
|
}
|
||||||
|
|
||||||
class Token {
|
class Token {
|
||||||
// Punctuators.
|
// Punctuators.
|
||||||
static leftParen { "leftParen" }
|
static leftParen { "leftParen" }
|
||||||
|
|||||||
@ -3,13 +3,6 @@ static const char* replModuleSource =
|
|||||||
"import \"meta\" for Meta\n"
|
"import \"meta\" for Meta\n"
|
||||||
"import \"io\" for Stdin\n"
|
"import \"io\" for Stdin\n"
|
||||||
"\n"
|
"\n"
|
||||||
"class EscapeBracket {\n"
|
|
||||||
" static up { 65 }\n"
|
|
||||||
" static down { 66 }\n"
|
|
||||||
" static right { 67 }\n"
|
|
||||||
" static left { 68 }\n"
|
|
||||||
"}\n"
|
|
||||||
"\n"
|
|
||||||
"class Repl {\n"
|
"class Repl {\n"
|
||||||
" construct new() {\n"
|
" construct new() {\n"
|
||||||
" _cursor = 0\n"
|
" _cursor = 0\n"
|
||||||
@ -21,7 +14,7 @@ static const char* replModuleSource =
|
|||||||
"\n"
|
"\n"
|
||||||
" run() {\n"
|
" run() {\n"
|
||||||
" Stdin.isRaw = true\n"
|
" Stdin.isRaw = true\n"
|
||||||
" refreshLine()\n"
|
" refreshLine(false)\n"
|
||||||
"\n"
|
"\n"
|
||||||
" while (true) {\n"
|
" while (true) {\n"
|
||||||
" var byte = Stdin.readByte()\n"
|
" var byte = Stdin.readByte()\n"
|
||||||
@ -34,7 +27,7 @@ static const char* replModuleSource =
|
|||||||
" return\n"
|
" return\n"
|
||||||
" } else if (byte == Chars.ctrlD) {\n"
|
" } else if (byte == Chars.ctrlD) {\n"
|
||||||
" // If the line is empty, Ctrl_D exits.\n"
|
" // If the line is empty, Ctrl_D exits.\n"
|
||||||
" if (!_line.isEmpty) {\n"
|
" if (_line.isEmpty) {\n"
|
||||||
" System.print()\n"
|
" System.print()\n"
|
||||||
" return\n"
|
" return\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
@ -45,6 +38,12 @@ static const char* replModuleSource =
|
|||||||
" _cursor = _line.count\n"
|
" _cursor = _line.count\n"
|
||||||
" } else if (byte == Chars.ctrlF) {\n"
|
" } else if (byte == Chars.ctrlF) {\n"
|
||||||
" cursorRight()\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"
|
" } else if (byte == Chars.ctrlK) {\n"
|
||||||
" // Delete everything after the cursor.\n"
|
" // Delete everything after the cursor.\n"
|
||||||
" _line = _line[0..._cursor]\n"
|
" _line = _line[0..._cursor]\n"
|
||||||
@ -73,14 +72,13 @@ static const char* replModuleSource =
|
|||||||
" // TODO: Ctrl-T to swap chars.\n"
|
" // TODO: Ctrl-T to swap chars.\n"
|
||||||
" // TODO: ESC H and F to move to beginning and end of line. (Both ESC\n"
|
" // TODO: ESC H and F to move to beginning and end of line. (Both ESC\n"
|
||||||
" // [ and ESC 0 sequences?)\n"
|
" // [ and ESC 0 sequences?)\n"
|
||||||
" // TODO: Ctrl-L clear screen.\n"
|
|
||||||
" // TODO: Ctrl-W delete previous word.\n"
|
" // TODO: Ctrl-W delete previous word.\n"
|
||||||
" // TODO: Completion.\n"
|
" // TODO: Completion.\n"
|
||||||
" // TODO: Other shortcuts?\n"
|
" // TODO: Other shortcuts?\n"
|
||||||
" System.print(\"Unhandled byte: %(byte)\")\n"
|
" System.print(\"Unhandled byte: %(byte)\")\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
"\n"
|
"\n"
|
||||||
" refreshLine()\n"
|
" refreshLine(true)\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
"\n"
|
"\n"
|
||||||
@ -160,6 +158,9 @@ static const char* replModuleSource =
|
|||||||
" }\n"
|
" }\n"
|
||||||
"\n"
|
"\n"
|
||||||
" executeInput() {\n"
|
" executeInput() {\n"
|
||||||
|
" // Remove the completion hint.\n"
|
||||||
|
" refreshLine(false)\n"
|
||||||
|
"\n"
|
||||||
" // Add it to the history.\n"
|
" // Add it to the history.\n"
|
||||||
" _history.add(_line)\n"
|
" _history.add(_line)\n"
|
||||||
" _historyIndex = _history.count\n"
|
" _historyIndex = _history.count\n"
|
||||||
@ -194,12 +195,14 @@ static const char* replModuleSource =
|
|||||||
" var fiber\n"
|
" var fiber\n"
|
||||||
" if (isStatement) {\n"
|
" if (isStatement) {\n"
|
||||||
" fiber = Fiber.new {\n"
|
" fiber = Fiber.new {\n"
|
||||||
|
" // TODO: Should evaluate in main module, not repl's own.\n"
|
||||||
" Meta.eval(input)\n"
|
" Meta.eval(input)\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
"\n"
|
"\n"
|
||||||
" var result = fiber.try()\n"
|
" var result = fiber.try()\n"
|
||||||
" if (fiber.error == null) return\n"
|
" if (fiber.error == null) return\n"
|
||||||
" } else {\n"
|
" } else {\n"
|
||||||
|
" // TODO: Should evaluate in main module, not repl's own.\n"
|
||||||
" var function = Meta.compileExpression(input)\n"
|
" var function = Meta.compileExpression(input)\n"
|
||||||
" if (function == null) return\n"
|
" if (function == null) return\n"
|
||||||
"\n"
|
"\n"
|
||||||
@ -207,6 +210,9 @@ static const char* replModuleSource =
|
|||||||
" var result = fiber.try()\n"
|
" var result = fiber.try()\n"
|
||||||
" if (fiber.error == null) {\n"
|
" if (fiber.error == null) {\n"
|
||||||
" // TODO: Handle error in result.toString.\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"
|
" System.print(\"%(Color.brightWhite)%(result)%(Color.none)\")\n"
|
||||||
" return\n"
|
" return\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
@ -232,7 +238,7 @@ static const char* replModuleSource =
|
|||||||
" return tokens\n"
|
" return tokens\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
"\n"
|
"\n"
|
||||||
" refreshLine() {\n"
|
" refreshLine(showCompletion) {\n"
|
||||||
" // Erase the whole line.\n"
|
" // Erase the whole line.\n"
|
||||||
" System.write(\"\x1b[2K\")\n"
|
" System.write(\"\x1b[2K\")\n"
|
||||||
"\n"
|
"\n"
|
||||||
@ -250,9 +256,32 @@ static const char* replModuleSource =
|
|||||||
" System.write(Color.none)\n"
|
" System.write(Color.none)\n"
|
||||||
" }\n"
|
" }\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"
|
" // Position the cursor.\n"
|
||||||
" System.write(\"\r\x1b[%(2 + _cursor)C\")\n"
|
" System.write(\"\r\x1b[%(2 + _cursor)C\")\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"
|
||||||
|
" if (name.startsWith(_line)) {\n"
|
||||||
|
" return name[_line.count..-1]\n"
|
||||||
|
" }\n"
|
||||||
|
" }\n"
|
||||||
|
" }\n"
|
||||||
"}\n"
|
"}\n"
|
||||||
"\n"
|
"\n"
|
||||||
"/// ANSI color escape sequences.\n"
|
"/// ANSI color escape sequences.\n"
|
||||||
@ -354,6 +383,13 @@ static const char* replModuleSource =
|
|||||||
" static isWhitespace(c) { c == space || c == tab || c == carriageReturn }\n"
|
" static isWhitespace(c) { c == space || c == tab || c == carriageReturn }\n"
|
||||||
"}\n"
|
"}\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
"class EscapeBracket {\n"
|
||||||
|
" static up { 0x41 }\n"
|
||||||
|
" static down { 0x42 }\n"
|
||||||
|
" static right { 0x43 }\n"
|
||||||
|
" static left { 0x44 }\n"
|
||||||
|
"}\n"
|
||||||
|
"\n"
|
||||||
"class Token {\n"
|
"class Token {\n"
|
||||||
" // Punctuators.\n"
|
" // Punctuators.\n"
|
||||||
" static leftParen { \"leftParen\" }\n"
|
" static leftParen { \"leftParen\" }\n"
|
||||||
|
|||||||
@ -36,6 +36,34 @@ void metaCompile(WrenVM* vm)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void metaGetModuleVariables(WrenVM* vm) {
|
||||||
|
wrenEnsureSlots(vm, 3);
|
||||||
|
|
||||||
|
Value moduleValue = wrenMapGet(vm->modules, vm->apiStack[1]);
|
||||||
|
if (IS_UNDEFINED(moduleValue))
|
||||||
|
{
|
||||||
|
vm->apiStack[0] = NULL_VAL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjModule* module = AS_MODULE(moduleValue);
|
||||||
|
ObjList* names = wrenNewList(vm, module->variableNames.count);
|
||||||
|
vm->apiStack[0] = OBJ_VAL(names);
|
||||||
|
|
||||||
|
// Initialize the elements to null in case a collection happens when we
|
||||||
|
// allocate the strings below.
|
||||||
|
for (int i = 0; i < names->elements.count; i++)
|
||||||
|
{
|
||||||
|
names->elements.data[i] = NULL_VAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < names->elements.count; i++)
|
||||||
|
{
|
||||||
|
String* name = &module->variableNames.data[i];
|
||||||
|
names->elements.data[i] = wrenNewString(vm, name->buffer, name->length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const char* wrenMetaSource()
|
const char* wrenMetaSource()
|
||||||
{
|
{
|
||||||
return metaModuleSource;
|
return metaModuleSource;
|
||||||
@ -49,9 +77,19 @@ 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.");
|
|
||||||
|
|
||||||
return metaCompile;
|
if (strcmp(signature, "compile_(_,_,_)") == 0)
|
||||||
|
{
|
||||||
|
return metaCompile;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(signature, "getModuleVariables_(_)") == 0)
|
||||||
|
{
|
||||||
|
return metaGetModuleVariables;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT(false, "Unknown method.");
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -1,4 +1,12 @@
|
|||||||
class Meta {
|
class Meta {
|
||||||
|
static getModuleVariables(module) {
|
||||||
|
if (!(module is String)) Fiber.abort("Module name must be a string.")
|
||||||
|
var result = getModuleVariables_(module)
|
||||||
|
if (result != null) return result
|
||||||
|
|
||||||
|
Fiber.abort("Could not find a module named '%(module)'.")
|
||||||
|
}
|
||||||
|
|
||||||
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.")
|
||||||
|
|
||||||
@ -15,4 +23,5 @@ class Meta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreign static compile_(source, isExpression, printErrors)
|
foreign static compile_(source, isExpression, printErrors)
|
||||||
|
foreign static getModuleVariables_(module)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,14 @@
|
|||||||
// Generated automatically from src/optional/wren_opt_meta.wren. Do not edit.
|
// Generated automatically from src/optional/wren_opt_meta.wren. Do not edit.
|
||||||
static const char* metaModuleSource =
|
static const char* metaModuleSource =
|
||||||
"class Meta {\n"
|
"class Meta {\n"
|
||||||
|
" static getModuleVariables(module) {\n"
|
||||||
|
" if (!(module is String)) Fiber.abort(\"Module name must be a string.\")\n"
|
||||||
|
" var result = getModuleVariables_(module)\n"
|
||||||
|
" if (result != null) return result\n"
|
||||||
|
"\n"
|
||||||
|
" Fiber.abort(\"Could not find a module named '%(module)'.\")\n"
|
||||||
|
" }\n"
|
||||||
|
"\n"
|
||||||
" 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"
|
||||||
@ -17,4 +25,5 @@ static const char* metaModuleSource =
|
|||||||
" }\n"
|
" }\n"
|
||||||
"\n"
|
"\n"
|
||||||
" foreign static compile_(source, isExpression, printErrors)\n"
|
" foreign static compile_(source, isExpression, printErrors)\n"
|
||||||
|
" foreign static getModuleVariables_(module)\n"
|
||||||
"}\n";
|
"}\n";
|
||||||
|
|||||||
17
test/meta/get_module_variables.wren
Normal file
17
test/meta/get_module_variables.wren
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import "meta" for Meta
|
||||||
|
|
||||||
|
var variables = Meta.getModuleVariables("main")
|
||||||
|
|
||||||
|
// Includes implicitly imported core stuff.
|
||||||
|
System.print(variables.contains("Object")) // expect: true
|
||||||
|
System.print(variables.contains("Bool")) // expect: true
|
||||||
|
|
||||||
|
// Includes top level variables.
|
||||||
|
System.print(variables.contains("variables")) // expect: true
|
||||||
|
|
||||||
|
// Even ones declared later.
|
||||||
|
System.print(variables.contains("later")) // expect: true
|
||||||
|
|
||||||
|
var later = "values"
|
||||||
|
|
||||||
|
System.print(variables.contains("unknown")) // expect: false
|
||||||
3
test/meta/get_module_variables_not_string.wren
Normal file
3
test/meta/get_module_variables_not_string.wren
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import "meta" for Meta
|
||||||
|
|
||||||
|
Meta.getModuleVariables(123) // expect runtime error: Module name must be a string.
|
||||||
3
test/meta/get_module_variables_unknown_module.wren
Normal file
3
test/meta/get_module_variables_unknown_module.wren
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import "meta" for Meta
|
||||||
|
|
||||||
|
Meta.getModuleVariables("unknown") // expect runtime error: Could not find a module named 'unknown'.
|
||||||
Reference in New Issue
Block a user