Merge branch 'master' into libuv
# Conflicts: # project/xcode/wren.xcodeproj/project.pbxproj # script/wren.mk # src/cli/main.c # src/cli/vm.c # src/cli/vm.h # test/api/main.c
This commit is contained in:
+2
-2
@@ -12,14 +12,14 @@ int main(int argc, const char* argv[])
|
||||
fprintf(stderr, "Usage: wren [file]\n");
|
||||
return 64; // EX_USAGE.
|
||||
}
|
||||
|
||||
|
||||
if (argc == 1)
|
||||
{
|
||||
runRepl();
|
||||
}
|
||||
else if (argc == 2)
|
||||
{
|
||||
runFile(argv[1], NULL);
|
||||
runFile(argv[1]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
+51
-33
@@ -10,46 +10,59 @@
|
||||
// The single VM instance that the CLI uses.
|
||||
WrenVM* vm;
|
||||
|
||||
WrenBindForeignMethodFn externalBindForeign;
|
||||
static WrenBindForeignMethodFn bindMethodFn = NULL;
|
||||
static WrenBindForeignClassFn bindClassFn = NULL;
|
||||
|
||||
uv_loop_t* loop;
|
||||
|
||||
static WrenForeignMethodFn bindForeignMethod(WrenVM* vm,
|
||||
const char* module,
|
||||
const char* className,
|
||||
bool isStatic,
|
||||
const char* signature)
|
||||
/// Binds foreign methods declared in either built in modules, or the injected
|
||||
/// API test modules.
|
||||
static WrenForeignMethodFn bindForeignMethod(WrenVM* vm, const char* module,
|
||||
const char* className, bool isStatic, const char* signature)
|
||||
{
|
||||
if (strcmp(module, "timer") == 0)
|
||||
{
|
||||
return timerBindForeign(vm, className, isStatic, signature);
|
||||
}
|
||||
|
||||
if (externalBindForeign != NULL)
|
||||
|
||||
if (bindMethodFn != NULL)
|
||||
{
|
||||
return externalBindForeign(vm, module, className, isStatic, signature);
|
||||
return bindMethodFn(vm, module, className, isStatic, signature);
|
||||
}
|
||||
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/// Binds foreign classes declared in either built in modules, or the injected
|
||||
/// API test modules.
|
||||
static WrenForeignClassMethods bindForeignClass(
|
||||
WrenVM* vm, const char* module, const char* className)
|
||||
{
|
||||
WrenForeignClassMethods methods = { NULL, NULL };
|
||||
|
||||
// TODO: Bind classes for built-in modules here.
|
||||
|
||||
if (bindClassFn != NULL)
|
||||
{
|
||||
return bindClassFn(vm, module, className);
|
||||
}
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
static void initVM()
|
||||
{
|
||||
WrenConfiguration config;
|
||||
wrenInitConfiguration(&config);
|
||||
|
||||
config.bindForeignMethodFn = bindForeignMethod;
|
||||
config.bindForeignClassFn = bindForeignClass;
|
||||
config.loadModuleFn = readModule;
|
||||
|
||||
// Since we're running in a standalone process, be generous with memory.
|
||||
config.initialHeapSize = 1024 * 1024 * 100;
|
||||
|
||||
// Use defaults for these.
|
||||
config.reallocateFn = NULL;
|
||||
config.minHeapSize = 0;
|
||||
config.heapGrowthPercent = 0;
|
||||
|
||||
vm = wrenNewVM(&config);
|
||||
|
||||
|
||||
// Initialize the event loop.
|
||||
loop = (uv_loop_t*)malloc(sizeof(uv_loop_t));
|
||||
uv_loop_init(loop);
|
||||
@@ -58,17 +71,15 @@ static void initVM()
|
||||
static void freeVM()
|
||||
{
|
||||
timerReleaseMethods();
|
||||
|
||||
|
||||
uv_loop_close(loop);
|
||||
free(loop);
|
||||
|
||||
wrenFreeVM(vm);
|
||||
}
|
||||
|
||||
void runFile(const char* path, WrenBindForeignMethodFn bindForeign)
|
||||
void runFile(const char* path)
|
||||
{
|
||||
externalBindForeign = bindForeign;
|
||||
|
||||
// Use the directory where the file is as the root to resolve imports
|
||||
// relative to.
|
||||
char* root = NULL;
|
||||
@@ -87,18 +98,18 @@ void runFile(const char* path, WrenBindForeignMethodFn bindForeign)
|
||||
fprintf(stderr, "Could not find file \"%s\".\n", path);
|
||||
exit(66);
|
||||
}
|
||||
|
||||
|
||||
initVM();
|
||||
|
||||
|
||||
WrenInterpretResult result = wrenInterpret(vm, path, source);
|
||||
|
||||
|
||||
if (result == WREN_RESULT_SUCCESS)
|
||||
{
|
||||
uv_run(loop, UV_RUN_DEFAULT);
|
||||
}
|
||||
|
||||
freeVM();
|
||||
|
||||
|
||||
free(source);
|
||||
free(root);
|
||||
|
||||
@@ -110,30 +121,30 @@ void runFile(const char* path, WrenBindForeignMethodFn bindForeign)
|
||||
int runRepl()
|
||||
{
|
||||
initVM();
|
||||
|
||||
|
||||
printf("\\\\/\"-\n");
|
||||
printf(" \\_/ wren v0.0.0\n");
|
||||
|
||||
|
||||
char line[MAX_LINE_LENGTH];
|
||||
|
||||
|
||||
for (;;)
|
||||
{
|
||||
printf("> ");
|
||||
|
||||
|
||||
if (!fgets(line, MAX_LINE_LENGTH, stdin))
|
||||
{
|
||||
printf("\n");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// TODO: Handle failure.
|
||||
wrenInterpret(vm, "Prompt", line);
|
||||
|
||||
|
||||
// TODO: Automatically print the result of expressions.
|
||||
}
|
||||
|
||||
|
||||
freeVM();
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -146,3 +157,10 @@ uv_loop_t* getLoop()
|
||||
{
|
||||
return loop;
|
||||
}
|
||||
|
||||
void setForeignCallbacks(
|
||||
WrenBindForeignMethodFn bindMethod, WrenBindForeignClassFn bindClass)
|
||||
{
|
||||
bindMethodFn = bindMethod;
|
||||
bindClassFn = bindClass;
|
||||
}
|
||||
|
||||
+9
-3
@@ -6,9 +6,8 @@
|
||||
|
||||
// Executes the Wren script at [path] in a new VM.
|
||||
//
|
||||
// If [bindForeign] is not `NULL`, it will be called to register any foreign
|
||||
// methods that the CLI itself doesn't handle.
|
||||
void runFile(const char* path, WrenBindForeignMethodFn bindForeign);
|
||||
// Exits if the script failed or could not be loaded.
|
||||
void runFile(const char* path);
|
||||
|
||||
// Runs the Wren interactive REPL.
|
||||
int runRepl();
|
||||
@@ -19,4 +18,11 @@ WrenVM* getVM();
|
||||
// Gets the event loop the VM is using.
|
||||
uv_loop_t* getLoop();
|
||||
|
||||
// Adds additional callbacks to use when binding foreign members from Wren.
|
||||
//
|
||||
// Used by the API test executable to let it wire up its own foreign functions.
|
||||
// This must be called before calling [createVM()].
|
||||
void setForeignCallbacks(WrenBindForeignMethodFn bindMethod,
|
||||
WrenBindForeignClassFn bindClass);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -53,6 +53,26 @@ typedef WrenForeignMethodFn (*WrenBindForeignMethodFn)(WrenVM* vm,
|
||||
bool isStatic,
|
||||
const char* signature);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// The callback invoked when the foreign object is created.
|
||||
//
|
||||
// This must be provided. Inside the body of this, it must call
|
||||
// [wrenAllocateForeign] exactly once.
|
||||
WrenForeignMethodFn allocate;
|
||||
|
||||
// The callback invoked when the garbage collector is about to collecto a
|
||||
// foreign object's memory.
|
||||
//
|
||||
// This may be `NULL` if the foreign class does not need to finalize.
|
||||
WrenForeignMethodFn finalize;
|
||||
} WrenForeignClassMethods;
|
||||
|
||||
// Returns a pair of pointers to the foreign methods used to allocate and
|
||||
// finalize the data for instances of [className] in [module].
|
||||
typedef WrenForeignClassMethods (*WrenBindForeignClassFn)(
|
||||
WrenVM* vm, const char* module, const char* className);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// The callback Wren will use to allocate, reallocate, and deallocate memory.
|
||||
@@ -88,6 +108,14 @@ typedef struct
|
||||
// Wren will report it as runtime error.
|
||||
WrenBindForeignMethodFn bindForeignMethodFn;
|
||||
|
||||
// The callback Wren uses to find a foreign class and get its foreign methods.
|
||||
//
|
||||
// When a foreign class is declared, this will be called with the class's
|
||||
// module and name when the class body is executed. It should return the
|
||||
// foreign functions uses to allocate and (optionally) finalize the bytes
|
||||
// stored in the foreign object when an instance is created.
|
||||
WrenBindForeignClassFn bindForeignClassFn;
|
||||
|
||||
// The number of bytes Wren will allocate before triggering the first garbage
|
||||
// collection.
|
||||
//
|
||||
@@ -129,6 +157,11 @@ typedef enum {
|
||||
WREN_RESULT_RUNTIME_ERROR
|
||||
} WrenInterpretResult;
|
||||
|
||||
// Initializes [configuration] with all of its default values.
|
||||
//
|
||||
// Call this before setting the particular fields you care about.
|
||||
void wrenInitConfiguration(WrenConfiguration* configuration);
|
||||
|
||||
// Creates a new Wren virtual machine using the given [configuration]. Wren
|
||||
// will copy the configuration data, so the argument passed to this can be
|
||||
// freed after calling this. If [configuration] is `NULL`, uses a default
|
||||
@@ -186,6 +219,16 @@ void wrenReleaseMethod(WrenVM* vm, WrenMethod* method);
|
||||
// longer be used.
|
||||
void wrenReleaseValue(WrenVM* vm, WrenValue* value);
|
||||
|
||||
// This must be called once inside a foreign class's allocator function.
|
||||
//
|
||||
// It tells Wren how many bytes of raw data need to be stored in the foreign
|
||||
// object and creates the new object with that size. It returns a pointer to
|
||||
// the foreign object's data.
|
||||
void* wrenAllocateForeign(WrenVM* vm, size_t size);
|
||||
|
||||
// Returns the number of arguments available to the current foreign method.
|
||||
int wrenGetArgumentCount(WrenVM* vm);
|
||||
|
||||
// The following functions read one of the arguments passed to a foreign call.
|
||||
// They may only be called while within a function provided to
|
||||
// [wrenDefineMethod] or [wrenDefineStaticMethod] that Wren has invoked.
|
||||
@@ -214,6 +257,11 @@ bool wrenGetArgumentBool(WrenVM* vm, int index);
|
||||
// a number.
|
||||
double wrenGetArgumentDouble(WrenVM* vm, int index);
|
||||
|
||||
// Reads a foreign object argument for a foreign call and returns a pointer to
|
||||
// the foreign data stored with it. Returns NULL if the argument is not a
|
||||
// foreign object.
|
||||
void* wrenGetArgumentForeign(WrenVM* vm, int index);
|
||||
|
||||
// Reads an string argument for a foreign call. Returns NULL if the argument is
|
||||
// not a string.
|
||||
//
|
||||
|
||||
@@ -99,10 +99,10 @@
|
||||
// foo() // No-argument method.
|
||||
// foo(_) // One-argument method.
|
||||
// foo(_,_) // Two-argument method.
|
||||
// this foo() // Constructor initializer.
|
||||
// init foo() // Constructor initializer.
|
||||
//
|
||||
// The maximum signature length takes into account the longest method name, the
|
||||
// maximum number of parameters with separators between them, "this ", and "()".
|
||||
// maximum number of parameters with separators between them, "init ", and "()".
|
||||
#define MAX_METHOD_SIGNATURE (MAX_METHOD_NAME + (MAX_PARAMETERS * 2) + 6)
|
||||
|
||||
// The maximum length of an identifier. The only real reason for this limitation
|
||||
|
||||
+150
-161
@@ -154,14 +154,9 @@ typedef struct
|
||||
|
||||
// If a syntax or compile error has occurred.
|
||||
bool hasError;
|
||||
|
||||
// A buffer for the unescaped text of the current token if it's a string
|
||||
// literal. Unlike the raw token, this will have escape sequences translated
|
||||
// to their literal equivalent.
|
||||
ByteBuffer string;
|
||||
|
||||
// If a number literal is currently being parsed this will hold its value.
|
||||
double number;
|
||||
|
||||
// The parsed value if the current token is a literal.
|
||||
Value value;
|
||||
} Parser;
|
||||
|
||||
typedef struct
|
||||
@@ -252,6 +247,9 @@ typedef struct
|
||||
// Symbol table for the fields of the class.
|
||||
SymbolTable* fields;
|
||||
|
||||
// True if the class being compiled is a foreign class.
|
||||
bool isForeign;
|
||||
|
||||
// True if the current method being compiled is static.
|
||||
bool inStatic;
|
||||
|
||||
@@ -369,6 +367,8 @@ static void error(Compiler* compiler, const char* format, ...)
|
||||
// Adds [constant] to the constant pool and returns its index.
|
||||
static int addConstant(Compiler* compiler, Value constant)
|
||||
{
|
||||
if (compiler->parser->hasError) return -1;
|
||||
|
||||
if (compiler->constants.count < MAX_CONSTANTS)
|
||||
{
|
||||
if (IS_OBJ(constant)) wrenPushRoot(compiler->parser->vm, AS_OBJ(constant));
|
||||
@@ -476,6 +476,14 @@ static char nextChar(Parser* parser)
|
||||
return c;
|
||||
}
|
||||
|
||||
// If the current character is [c], consumes it and returns `true`.
|
||||
static bool matchChar(Parser* parser, char c)
|
||||
{
|
||||
if (peekChar(parser) != c) return false;
|
||||
nextChar(parser);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Sets the parser's current token to the given [type] and current character
|
||||
// range.
|
||||
static void makeToken(Parser* parser, TokenType type)
|
||||
@@ -493,14 +501,7 @@ static void makeToken(Parser* parser, TokenType type)
|
||||
// [two]. Otherwise makes a token of type [one].
|
||||
static void twoCharToken(Parser* parser, char c, TokenType two, TokenType one)
|
||||
{
|
||||
if (peekChar(parser) == c)
|
||||
{
|
||||
nextChar(parser);
|
||||
makeToken(parser, two);
|
||||
return;
|
||||
}
|
||||
|
||||
makeToken(parser, one);
|
||||
makeToken(parser, matchChar(parser, c) ? two : one);
|
||||
}
|
||||
|
||||
// Skips the rest of the current line.
|
||||
@@ -515,8 +516,6 @@ static void skipLineComment(Parser* parser)
|
||||
// Skips the rest of a block comment.
|
||||
static void skipBlockComment(Parser* parser)
|
||||
{
|
||||
nextChar(parser); // The opening "*".
|
||||
|
||||
int nesting = 1;
|
||||
while (nesting > 0)
|
||||
{
|
||||
@@ -578,13 +577,13 @@ static void makeNumber(Parser* parser, bool isHex)
|
||||
|
||||
// We don't check that the entire token is consumed because we've already
|
||||
// scanned it ourselves and know it's valid.
|
||||
parser->number = isHex ? strtol(parser->tokenStart, NULL, 16)
|
||||
: strtod(parser->tokenStart, NULL);
|
||||
parser->value = NUM_VAL(isHex ? strtol(parser->tokenStart, NULL, 16)
|
||||
: strtod(parser->tokenStart, NULL));
|
||||
|
||||
if (errno == ERANGE)
|
||||
{
|
||||
lexError(parser, "Number literal was too large.");
|
||||
parser->number = 0;
|
||||
parser->value = NUM_VAL(0);
|
||||
}
|
||||
|
||||
makeToken(parser, TOKEN_NUMBER);
|
||||
@@ -605,7 +604,6 @@ static void readHexNumber(Parser* parser)
|
||||
// Finishes lexing a number literal.
|
||||
static void readNumber(Parser* parser)
|
||||
{
|
||||
// TODO: scientific, etc.
|
||||
while (isDigit(peekChar(parser))) nextChar(parser);
|
||||
|
||||
// See if it has a floating point. Make sure there is a digit after the "."
|
||||
@@ -615,6 +613,20 @@ static void readNumber(Parser* parser)
|
||||
nextChar(parser);
|
||||
while (isDigit(peekChar(parser))) nextChar(parser);
|
||||
}
|
||||
|
||||
// See if the number is in scientific notation.
|
||||
if (matchChar(parser, 'e') || matchChar(parser, 'E'))
|
||||
{
|
||||
// Allow a negative exponent.
|
||||
matchChar(parser, '-');
|
||||
|
||||
if (!isDigit(peekChar(parser)))
|
||||
{
|
||||
lexError(parser, "Unterminated scientific notation.");
|
||||
}
|
||||
|
||||
while (isDigit(peekChar(parser))) nextChar(parser);
|
||||
}
|
||||
|
||||
makeNumber(parser, false);
|
||||
}
|
||||
@@ -650,12 +662,6 @@ static void readName(Parser* parser, TokenType type)
|
||||
makeToken(parser, type);
|
||||
}
|
||||
|
||||
// Adds [c] to the current string literal being tokenized.
|
||||
static void addStringChar(Parser* parser, char c)
|
||||
{
|
||||
wrenByteBufferWrite(parser->vm, &parser->string, c);
|
||||
}
|
||||
|
||||
// Reads [digits] hex digits in a string literal and returns their number value.
|
||||
static int readHexEscape(Parser* parser, int digits, const char* description)
|
||||
{
|
||||
@@ -686,7 +692,7 @@ static int readHexEscape(Parser* parser, int digits, const char* description)
|
||||
}
|
||||
|
||||
// Reads a four hex digit Unicode escape sequence in a string literal.
|
||||
static void readUnicodeEscape(Parser* parser)
|
||||
static void readUnicodeEscape(Parser* parser, ByteBuffer* string)
|
||||
{
|
||||
int value = readHexEscape(parser, 4, "Unicode");
|
||||
|
||||
@@ -694,16 +700,16 @@ static void readUnicodeEscape(Parser* parser)
|
||||
int numBytes = wrenUtf8NumBytes(value);
|
||||
if (numBytes != 0)
|
||||
{
|
||||
wrenByteBufferFill(parser->vm, &parser->string, 0, numBytes);
|
||||
wrenUtf8Encode(value,
|
||||
parser->string.data + parser->string.count - numBytes);
|
||||
wrenByteBufferFill(parser->vm, string, 0, numBytes);
|
||||
wrenUtf8Encode(value, string->data + string->count - numBytes);
|
||||
}
|
||||
}
|
||||
|
||||
// Finishes lexing a string literal.
|
||||
static void readString(Parser* parser)
|
||||
{
|
||||
wrenByteBufferClear(parser->vm, &parser->string);
|
||||
ByteBuffer string;
|
||||
wrenByteBufferInit(&string);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
@@ -724,20 +730,21 @@ static void readString(Parser* parser)
|
||||
{
|
||||
switch (nextChar(parser))
|
||||
{
|
||||
case '"': addStringChar(parser, '"'); break;
|
||||
case '\\': addStringChar(parser, '\\'); break;
|
||||
case '0': addStringChar(parser, '\0'); break;
|
||||
case 'a': addStringChar(parser, '\a'); break;
|
||||
case 'b': addStringChar(parser, '\b'); break;
|
||||
case 'f': addStringChar(parser, '\f'); break;
|
||||
case 'n': addStringChar(parser, '\n'); break;
|
||||
case 'r': addStringChar(parser, '\r'); break;
|
||||
case 't': addStringChar(parser, '\t'); break;
|
||||
case 'u': readUnicodeEscape(parser); break;
|
||||
case '"': wrenByteBufferWrite(parser->vm, &string, '"'); break;
|
||||
case '\\': wrenByteBufferWrite(parser->vm, &string, '\\'); break;
|
||||
case '0': wrenByteBufferWrite(parser->vm, &string, '\0'); break;
|
||||
case 'a': wrenByteBufferWrite(parser->vm, &string, '\a'); break;
|
||||
case 'b': wrenByteBufferWrite(parser->vm, &string, '\b'); break;
|
||||
case 'f': wrenByteBufferWrite(parser->vm, &string, '\f'); break;
|
||||
case 'n': wrenByteBufferWrite(parser->vm, &string, '\n'); break;
|
||||
case 'r': wrenByteBufferWrite(parser->vm, &string, '\r'); break;
|
||||
case 't': wrenByteBufferWrite(parser->vm, &string, '\t'); break;
|
||||
case 'u': readUnicodeEscape(parser, &string); break;
|
||||
// TODO: 'U' for 8 octet Unicode escapes.
|
||||
case 'v': addStringChar(parser, '\v'); break;
|
||||
case 'v': wrenByteBufferWrite(parser->vm, &string, '\v'); break;
|
||||
case 'x':
|
||||
addStringChar(parser, (uint8_t)readHexEscape(parser, 2, "byte"));
|
||||
wrenByteBufferWrite(parser->vm, &string,
|
||||
(uint8_t)readHexEscape(parser, 2, "byte"));
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -748,10 +755,12 @@ static void readString(Parser* parser)
|
||||
}
|
||||
else
|
||||
{
|
||||
addStringChar(parser, c);
|
||||
wrenByteBufferWrite(parser->vm, &string, c);
|
||||
}
|
||||
}
|
||||
|
||||
parser->value = wrenNewString(parser->vm, (char*)string.data, string.count);
|
||||
wrenByteBufferClear(parser->vm, &string);
|
||||
makeToken(parser, TOKEN_STRING);
|
||||
}
|
||||
|
||||
@@ -779,38 +788,38 @@ static void nextToken(Parser* parser)
|
||||
case '{': makeToken(parser, TOKEN_LEFT_BRACE); return;
|
||||
case '}': makeToken(parser, TOKEN_RIGHT_BRACE); return;
|
||||
case ':': makeToken(parser, TOKEN_COLON); return;
|
||||
case '.':
|
||||
if (peekChar(parser) == '.')
|
||||
{
|
||||
nextChar(parser);
|
||||
if (peekChar(parser) == '.')
|
||||
{
|
||||
nextChar(parser);
|
||||
makeToken(parser, TOKEN_DOTDOTDOT);
|
||||
return;
|
||||
}
|
||||
|
||||
makeToken(parser, TOKEN_DOTDOT);
|
||||
return;
|
||||
}
|
||||
|
||||
makeToken(parser, TOKEN_DOT);
|
||||
return;
|
||||
|
||||
case ',': makeToken(parser, TOKEN_COMMA); return;
|
||||
case '*': makeToken(parser, TOKEN_STAR); return;
|
||||
case '%': makeToken(parser, TOKEN_PERCENT); return;
|
||||
case '^': makeToken(parser, TOKEN_CARET); return;
|
||||
case '+': makeToken(parser, TOKEN_PLUS); return;
|
||||
case '-': makeToken(parser, TOKEN_MINUS); return;
|
||||
case '~': makeToken(parser, TOKEN_TILDE); return;
|
||||
case '?': makeToken(parser, TOKEN_QUESTION); return;
|
||||
|
||||
case '|': twoCharToken(parser, '|', TOKEN_PIPEPIPE, TOKEN_PIPE); return;
|
||||
case '&': twoCharToken(parser, '&', TOKEN_AMPAMP, TOKEN_AMP); return;
|
||||
case '=': twoCharToken(parser, '=', TOKEN_EQEQ, TOKEN_EQ); return;
|
||||
case '!': twoCharToken(parser, '=', TOKEN_BANGEQ, TOKEN_BANG); return;
|
||||
|
||||
case '.':
|
||||
if (matchChar(parser, '.'))
|
||||
{
|
||||
twoCharToken(parser, '.', TOKEN_DOTDOTDOT, TOKEN_DOTDOT);
|
||||
return;
|
||||
}
|
||||
|
||||
makeToken(parser, TOKEN_DOT);
|
||||
return;
|
||||
|
||||
case '/':
|
||||
if (peekChar(parser) == '/')
|
||||
if (matchChar(parser, '/'))
|
||||
{
|
||||
skipLineComment(parser);
|
||||
break;
|
||||
}
|
||||
|
||||
if (peekChar(parser) == '*')
|
||||
if (matchChar(parser, '*'))
|
||||
{
|
||||
skipBlockComment(parser);
|
||||
break;
|
||||
@@ -819,30 +828,9 @@ static void nextToken(Parser* parser)
|
||||
makeToken(parser, TOKEN_SLASH);
|
||||
return;
|
||||
|
||||
case '-':
|
||||
makeToken(parser, TOKEN_MINUS);
|
||||
return;
|
||||
|
||||
case '|':
|
||||
twoCharToken(parser, '|', TOKEN_PIPEPIPE, TOKEN_PIPE);
|
||||
return;
|
||||
|
||||
case '&':
|
||||
twoCharToken(parser, '&', TOKEN_AMPAMP, TOKEN_AMP);
|
||||
return;
|
||||
|
||||
case '^':
|
||||
makeToken(parser, TOKEN_CARET);
|
||||
return;
|
||||
|
||||
case '=':
|
||||
twoCharToken(parser, '=', TOKEN_EQEQ, TOKEN_EQ);
|
||||
return;
|
||||
|
||||
case '<':
|
||||
if (peekChar(parser) == '<')
|
||||
if (matchChar(parser, '<'))
|
||||
{
|
||||
nextChar(parser);
|
||||
makeToken(parser, TOKEN_LTLT);
|
||||
}
|
||||
else
|
||||
@@ -852,9 +840,8 @@ static void nextToken(Parser* parser)
|
||||
return;
|
||||
|
||||
case '>':
|
||||
if (peekChar(parser) == '>')
|
||||
if (matchChar(parser, '>'))
|
||||
{
|
||||
nextChar(parser);
|
||||
makeToken(parser, TOKEN_GTGT);
|
||||
}
|
||||
else
|
||||
@@ -863,10 +850,6 @@ static void nextToken(Parser* parser)
|
||||
}
|
||||
return;
|
||||
|
||||
case '!':
|
||||
twoCharToken(parser, '=', TOKEN_BANGEQ, TOKEN_BANG);
|
||||
return;
|
||||
|
||||
case '\n':
|
||||
makeToken(parser, TOKEN_LINE);
|
||||
return;
|
||||
@@ -974,9 +957,7 @@ static bool matchLine(Compiler* compiler)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Consumes the current token if its type is [expected]. Returns true if a
|
||||
// token was consumed. Since [expected] is known to be in the middle of an
|
||||
// expression, any newlines following it are consumed and discarded.
|
||||
// Discards any newlines starting at the current token.
|
||||
static void ignoreNewlines(Compiler* compiler)
|
||||
{
|
||||
matchLine(compiler);
|
||||
@@ -1037,9 +1018,19 @@ static int emitJump(Compiler* compiler, Code instruction)
|
||||
return emit(compiler, 0xff) - 1;
|
||||
}
|
||||
|
||||
// Creates a new constant for the current value and emits the bytecode to load
|
||||
// it from the constant table.
|
||||
static void emitConstant(Compiler* compiler)
|
||||
{
|
||||
int constant = addConstant(compiler, compiler->parser->value);
|
||||
|
||||
// Compile the code to load the constant.
|
||||
emitShortArg(compiler, CODE_CONSTANT, constant);
|
||||
}
|
||||
|
||||
// Create a new local variable with [name]. Assumes the current scope is local
|
||||
// and the name is unique.
|
||||
static int defineLocal(Compiler* compiler, const char* name, int length)
|
||||
static int addLocal(Compiler* compiler, const char* name, int length)
|
||||
{
|
||||
Local* local = &compiler->locals[compiler->numLocals];
|
||||
local->name = name;
|
||||
@@ -1105,7 +1096,7 @@ static int declareVariable(Compiler* compiler, Token* token)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return defineLocal(compiler, token->start, token->length);
|
||||
return addLocal(compiler, token->start, token->length);
|
||||
}
|
||||
|
||||
// Parses a name token and declares a variable in the current scope with that
|
||||
@@ -1595,7 +1586,7 @@ static void signatureToString(Signature* signature,
|
||||
break;
|
||||
|
||||
case SIG_INITIALIZER:
|
||||
memcpy(name, "this ", 5);
|
||||
memcpy(name, "init ", 5);
|
||||
memcpy(name + 5, signature->name, signature->length);
|
||||
*length = 5 + signature->length;
|
||||
signatureParameterList(name, length, signature->arity, '(', ')');
|
||||
@@ -1671,8 +1662,7 @@ static void callSignature(Compiler* compiler, Code instruction,
|
||||
// superclass in a constant. So, here, we create a slot in the constant
|
||||
// table and store NULL in it. When the method is bound, we'll look up the
|
||||
// superclass then and store it in the constant slot.
|
||||
int constant = addConstant(compiler, NULL_VAL);
|
||||
emitShort(compiler, constant);
|
||||
emitShort(compiler, addConstant(compiler, NULL_VAL));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1938,6 +1928,10 @@ static void field(Compiler* compiler, bool allowAssignment)
|
||||
{
|
||||
error(compiler, "Cannot reference a field outside of a class definition.");
|
||||
}
|
||||
else if (enclosingClass->isForeign)
|
||||
{
|
||||
error(compiler, "Cannot define fields in a foreign class.");
|
||||
}
|
||||
else if (enclosingClass->inStatic)
|
||||
{
|
||||
error(compiler, "Cannot use an instance field in a static method.");
|
||||
@@ -2128,32 +2122,10 @@ static void null(Compiler* compiler, bool allowAssignment)
|
||||
emit(compiler, CODE_NULL);
|
||||
}
|
||||
|
||||
static void number(Compiler* compiler, bool allowAssignment)
|
||||
// A number or string literal.
|
||||
static void literal(Compiler* compiler, bool allowAssignment)
|
||||
{
|
||||
int constant = addConstant(compiler, NUM_VAL(compiler->parser->number));
|
||||
|
||||
// Compile the code to load the constant.
|
||||
emitShortArg(compiler, CODE_CONSTANT, constant);
|
||||
}
|
||||
|
||||
// Parses a string literal and adds it to the constant table.
|
||||
static int stringConstant(Compiler* compiler)
|
||||
{
|
||||
// Define a constant for the literal.
|
||||
int constant = addConstant(compiler, wrenNewString(compiler->parser->vm,
|
||||
(char*)compiler->parser->string.data, compiler->parser->string.count));
|
||||
|
||||
wrenByteBufferClear(compiler->parser->vm, &compiler->parser->string);
|
||||
|
||||
return constant;
|
||||
}
|
||||
|
||||
static void string(Compiler* compiler, bool allowAssignment)
|
||||
{
|
||||
int constant = stringConstant(compiler);
|
||||
|
||||
// Compile the code to load the constant.
|
||||
emitShortArg(compiler, CODE_CONSTANT, constant);
|
||||
emitConstant(compiler);
|
||||
}
|
||||
|
||||
static void super_(Compiler* compiler, bool allowAssignment)
|
||||
@@ -2409,7 +2381,7 @@ void namedSignature(Compiler* compiler, Signature* signature)
|
||||
// Compiles a method signature for a constructor.
|
||||
void constructorSignature(Compiler* compiler, Signature* signature)
|
||||
{
|
||||
consume(compiler, TOKEN_NAME, "Expect constructor name after 'this'.");
|
||||
consume(compiler, TOKEN_NAME, "Expect constructor name after 'construct'.");
|
||||
|
||||
// Capture the name.
|
||||
*signature = signatureFromToken(compiler, SIG_INITIALIZER);
|
||||
@@ -2500,8 +2472,8 @@ GrammarRule rules[] =
|
||||
/* TOKEN_FIELD */ PREFIX(field),
|
||||
/* TOKEN_STATIC_FIELD */ PREFIX(staticField),
|
||||
/* TOKEN_NAME */ { name, NULL, namedSignature, PREC_NONE, NULL },
|
||||
/* TOKEN_NUMBER */ PREFIX(number),
|
||||
/* TOKEN_STRING */ PREFIX(string),
|
||||
/* TOKEN_NUMBER */ PREFIX(literal),
|
||||
/* TOKEN_STRING */ PREFIX(literal),
|
||||
/* TOKEN_LINE */ UNUSED,
|
||||
/* TOKEN_ERROR */ UNUSED,
|
||||
/* TOKEN_EOF */ UNUSED
|
||||
@@ -2591,6 +2563,8 @@ static int getNumArguments(const uint8_t* bytecode, const Value* constants,
|
||||
case CODE_LOAD_LOCAL_7:
|
||||
case CODE_LOAD_LOCAL_8:
|
||||
case CODE_CONSTRUCT:
|
||||
case CODE_FOREIGN_CONSTRUCT:
|
||||
case CODE_FOREIGN_CLASS:
|
||||
return 0;
|
||||
|
||||
case CODE_LOAD_LOCAL:
|
||||
@@ -2773,11 +2747,11 @@ static void forStatement(Compiler* compiler)
|
||||
// The space in the variable name ensures it won't collide with a user-defined
|
||||
// variable.
|
||||
expression(compiler);
|
||||
int seqSlot = defineLocal(compiler, "seq ", 4);
|
||||
int seqSlot = addLocal(compiler, "seq ", 4);
|
||||
|
||||
// Create another hidden local for the iterator object.
|
||||
null(compiler, false);
|
||||
int iterSlot = defineLocal(compiler, "iter ", 5);
|
||||
int iterSlot = addLocal(compiler, "iter ", 5);
|
||||
|
||||
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after loop expression.");
|
||||
|
||||
@@ -2805,7 +2779,7 @@ static void forStatement(Compiler* compiler)
|
||||
// Bind the loop variable in its own scope. This ensures we get a fresh
|
||||
// variable each iteration so that closures for it don't all see the same one.
|
||||
pushScope(compiler);
|
||||
defineLocal(compiler, name, length);
|
||||
addLocal(compiler, name, length);
|
||||
|
||||
loopBody(compiler);
|
||||
|
||||
@@ -2945,7 +2919,8 @@ static void createConstructor(Compiler* compiler, Signature* signature,
|
||||
initCompiler(&methodCompiler, compiler->parser, compiler, false);
|
||||
|
||||
// Allocate the instance.
|
||||
emit(&methodCompiler, CODE_CONSTRUCT);
|
||||
emit(&methodCompiler, compiler->enclosingClass->isForeign
|
||||
? CODE_FOREIGN_CONSTRUCT : CODE_CONSTRUCT);
|
||||
|
||||
// Run its initializer.
|
||||
emitShortArg(&methodCompiler, (Code)(CODE_CALL_0 + signature->arity),
|
||||
@@ -3027,9 +3002,9 @@ static bool method(Compiler* compiler, ClassCompiler* classCompiler,
|
||||
if (isForeign)
|
||||
{
|
||||
// Define a constant for the signature.
|
||||
int constant = addConstant(compiler, wrenNewString(compiler->parser->vm,
|
||||
fullSignature, length));
|
||||
emitShortArg(compiler, CODE_CONSTANT, constant);
|
||||
compiler->parser->value = wrenNewString(compiler->parser->vm,
|
||||
fullSignature, length);
|
||||
emitConstant(compiler);
|
||||
|
||||
// We don't need the function we started compiling in the parameter list
|
||||
// any more.
|
||||
@@ -3039,7 +3014,6 @@ static bool method(Compiler* compiler, ClassCompiler* classCompiler,
|
||||
{
|
||||
consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' to begin method body.");
|
||||
finishBody(&methodCompiler, signature.type == SIG_INITIALIZER);
|
||||
|
||||
endCompiler(&methodCompiler, fullSignature, length);
|
||||
}
|
||||
|
||||
@@ -3066,13 +3040,12 @@ static bool method(Compiler* compiler, ClassCompiler* classCompiler,
|
||||
|
||||
// Defines a default "new()" constructor on the current class.
|
||||
//
|
||||
// It just invokes "this new()" on the instance. If a base class defines that,
|
||||
// It just invokes "init new()" on the instance. If a base class defines that,
|
||||
// it will get invoked. Otherwise, it falls to the default one in Object which
|
||||
// does nothing.
|
||||
static void createDefaultConstructor(Compiler* compiler, int classSlot)
|
||||
{
|
||||
Signature signature = { "new", 3, SIG_INITIALIZER, 0 };
|
||||
|
||||
int initializerSymbol = signatureSymbol(compiler, &signature);
|
||||
|
||||
signature.type = SIG_METHOD;
|
||||
@@ -3083,17 +3056,16 @@ static void createDefaultConstructor(Compiler* compiler, int classSlot)
|
||||
}
|
||||
|
||||
// Compiles a class definition. Assumes the "class" token has already been
|
||||
// consumed.
|
||||
static void classDefinition(Compiler* compiler)
|
||||
// consumed (along with a possibly preceding "foreign" token).
|
||||
static void classDefinition(Compiler* compiler, bool isForeign)
|
||||
{
|
||||
// Create a variable to store the class in.
|
||||
int slot = declareNamedVariable(compiler);
|
||||
|
||||
// Make a string constant for the name.
|
||||
int nameConstant = addConstant(compiler, wrenNewString(compiler->parser->vm,
|
||||
compiler->parser->previous.start, compiler->parser->previous.length));
|
||||
|
||||
emitShortArg(compiler, CODE_CONSTANT, nameConstant);
|
||||
compiler->parser->value = wrenNewString(compiler->parser->vm,
|
||||
compiler->parser->previous.start, compiler->parser->previous.length);
|
||||
emitConstant(compiler);
|
||||
|
||||
// Load the superclass (if there is one).
|
||||
if (match(compiler, TOKEN_IS))
|
||||
@@ -3109,7 +3081,15 @@ static void classDefinition(Compiler* compiler)
|
||||
// Store a placeholder for the number of fields argument. We don't know
|
||||
// the value until we've compiled all the methods to see which fields are
|
||||
// used.
|
||||
int numFieldsInstruction = emitByteArg(compiler, CODE_CLASS, 255);
|
||||
int numFieldsInstruction = -1;
|
||||
if (isForeign)
|
||||
{
|
||||
emit(compiler, CODE_FOREIGN_CLASS);
|
||||
}
|
||||
else
|
||||
{
|
||||
numFieldsInstruction = emitByteArg(compiler, CODE_CLASS, 255);
|
||||
}
|
||||
|
||||
// Store it in its name.
|
||||
defineVariable(compiler, slot);
|
||||
@@ -3120,6 +3100,7 @@ static void classDefinition(Compiler* compiler)
|
||||
pushScope(compiler);
|
||||
|
||||
ClassCompiler classCompiler;
|
||||
classCompiler.isForeign = isForeign;
|
||||
|
||||
// Set up a symbol table for the class's fields. We'll initially compile
|
||||
// them to slots starting at zero. When the method is bound to the class, the
|
||||
@@ -3155,7 +3136,11 @@ static void classDefinition(Compiler* compiler)
|
||||
}
|
||||
|
||||
// Update the class with the number of fields.
|
||||
compiler->bytecode.data[numFieldsInstruction] = (uint8_t)fields.count;
|
||||
if (!isForeign)
|
||||
{
|
||||
compiler->bytecode.data[numFieldsInstruction] = (uint8_t)fields.count;
|
||||
}
|
||||
|
||||
wrenSymbolTableClear(compiler->parser->vm, &fields);
|
||||
|
||||
compiler->enclosingClass = NULL;
|
||||
@@ -3163,10 +3148,11 @@ static void classDefinition(Compiler* compiler)
|
||||
popScope(compiler);
|
||||
}
|
||||
|
||||
// Compiles an "import" statement.
|
||||
static void import(Compiler* compiler)
|
||||
{
|
||||
consume(compiler, TOKEN_STRING, "Expect a string after 'import'.");
|
||||
int moduleConstant = stringConstant(compiler);
|
||||
int moduleConstant = addConstant(compiler, compiler->parser->value);
|
||||
|
||||
// Load the module.
|
||||
emitShortArg(compiler, CODE_LOAD_MODULE, moduleConstant);
|
||||
@@ -3197,6 +3183,7 @@ static void import(Compiler* compiler)
|
||||
} while (match(compiler, TOKEN_COMMA));
|
||||
}
|
||||
|
||||
// Compiles a "var" variable definition statement.
|
||||
static void variableDefinition(Compiler* compiler)
|
||||
{
|
||||
// Grab its name, but don't declare it yet. A (local) variable shouldn't be
|
||||
@@ -3227,23 +3214,25 @@ void definition(Compiler* compiler)
|
||||
{
|
||||
if (match(compiler, TOKEN_CLASS))
|
||||
{
|
||||
classDefinition(compiler);
|
||||
return;
|
||||
classDefinition(compiler, false);
|
||||
}
|
||||
|
||||
if (match(compiler, TOKEN_IMPORT))
|
||||
else if (match(compiler, TOKEN_FOREIGN))
|
||||
{
|
||||
consume(compiler, TOKEN_CLASS, "Expect 'class' after 'foreign'.");
|
||||
classDefinition(compiler, true);
|
||||
}
|
||||
else if (match(compiler, TOKEN_IMPORT))
|
||||
{
|
||||
import(compiler);
|
||||
return;
|
||||
}
|
||||
|
||||
if (match(compiler, TOKEN_VAR))
|
||||
else if (match(compiler, TOKEN_VAR))
|
||||
{
|
||||
variableDefinition(compiler);
|
||||
return;
|
||||
}
|
||||
|
||||
block(compiler);
|
||||
else
|
||||
{
|
||||
block(compiler);
|
||||
}
|
||||
}
|
||||
|
||||
ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* sourcePath,
|
||||
@@ -3257,6 +3246,7 @@ ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* sourcePath,
|
||||
parser.module = module;
|
||||
parser.sourcePath = AS_STRING(sourcePathValue);
|
||||
parser.source = source;
|
||||
parser.value = UNDEFINED_VAL;
|
||||
|
||||
parser.tokenStart = source;
|
||||
parser.currentChar = source;
|
||||
@@ -3274,8 +3264,6 @@ ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* sourcePath,
|
||||
parser.printErrors = printErrors;
|
||||
parser.hasError = false;
|
||||
|
||||
wrenByteBufferInit(&parser.string);
|
||||
|
||||
// Read the first token.
|
||||
nextToken(&parser);
|
||||
|
||||
@@ -3384,6 +3372,7 @@ void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn)
|
||||
void wrenMarkCompiler(WrenVM* vm, Compiler* compiler)
|
||||
{
|
||||
wrenMarkObj(vm, (Obj*)compiler->parser->sourcePath);
|
||||
wrenMarkValue(vm, compiler->parser->value);
|
||||
|
||||
// Walk up the parent chain to mark the outer compilers too. The VM only
|
||||
// tracks the innermost one.
|
||||
|
||||
+1
-1
@@ -1291,7 +1291,7 @@ void wrenInitializeCore(WrenVM* vm)
|
||||
PRIMITIVE(vm->objectClass, "!", object_not);
|
||||
PRIMITIVE(vm->objectClass, "==(_)", object_eqeq);
|
||||
PRIMITIVE(vm->objectClass, "!=(_)", object_bangeq);
|
||||
PRIMITIVE(vm->objectClass, "this new()", return_this);
|
||||
PRIMITIVE(vm->objectClass, "init new()", return_this);
|
||||
PRIMITIVE(vm->objectClass, "is(_)", object_is);
|
||||
PRIMITIVE(vm->objectClass, "toString", object_toString);
|
||||
PRIMITIVE(vm->objectClass, "type", object_type);
|
||||
|
||||
+8
-2
@@ -32,10 +32,13 @@ static void dumpObject(Obj* obj)
|
||||
{
|
||||
switch (obj->type)
|
||||
{
|
||||
case OBJ_CLASS: printf("[class %p]", obj); break;
|
||||
case OBJ_CLASS:
|
||||
printf("[class %s %p]", ((ObjClass*)obj)->name->value, obj);
|
||||
break;
|
||||
case OBJ_CLOSURE: printf("[closure %p]", obj); break;
|
||||
case OBJ_FIBER: printf("[fiber %p]", obj); break;
|
||||
case OBJ_FN: printf("[fn %p]", obj); break;
|
||||
case OBJ_FOREIGN: printf("[foreign %p]", obj); break;
|
||||
case OBJ_INSTANCE: printf("[instance %p]", obj); break;
|
||||
case OBJ_LIST: printf("[list %p]", obj); break;
|
||||
case OBJ_MAP: printf("[map %p]", obj); break;
|
||||
@@ -269,7 +272,8 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine)
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_CONSTRUCT: printf("CODE_CONSTRUCT\n"); break;
|
||||
case CODE_CONSTRUCT: printf("CODE_CONSTRUCT\n"); break;
|
||||
case CODE_FOREIGN_CONSTRUCT: printf("CODE_FOREIGN_CONSTRUCT\n"); break;
|
||||
|
||||
case CODE_CLASS:
|
||||
{
|
||||
@@ -278,6 +282,8 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine)
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_FOREIGN_CLASS: printf("FOREIGN_CLASS\n"); break;
|
||||
|
||||
case CODE_METHOD_INSTANCE:
|
||||
{
|
||||
int symbol = READ_SHORT();
|
||||
|
||||
@@ -157,11 +157,22 @@ OPCODE(CLOSURE)
|
||||
// compiler-generated constructor metaclass methods.
|
||||
OPCODE(CONSTRUCT)
|
||||
|
||||
// Creates a new instance of a foreign class.
|
||||
//
|
||||
// Assumes the class object is in slot zero, and replaces it with the new
|
||||
// uninitialized instance of that class. This opcode is only emitted by the
|
||||
// compiler-generated constructor metaclass methods.
|
||||
OPCODE(FOREIGN_CONSTRUCT)
|
||||
|
||||
// Creates a class. Top of stack is the superclass, or `null` if the class
|
||||
// inherits Object. Below that is a string for the name of the class. Byte
|
||||
// [arg] is the number of fields in the class.
|
||||
OPCODE(CLASS)
|
||||
|
||||
// Creates a foreign class. Top of stack is the superclass, or `null` if the
|
||||
// class inherits Object. Below that is a string for the name of the class.
|
||||
OPCODE(FOREIGN_CLASS)
|
||||
|
||||
// Define a method for symbol [arg]. The class receiving the method is popped
|
||||
// off the stack, then the function defining the body is popped.
|
||||
//
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@
|
||||
|
||||
// Reusable data structures and other utility functions.
|
||||
|
||||
// A simple structure to keep trace of the string length as long as its data
|
||||
// (including the null-terminator)
|
||||
// A simple structure to keep track of a string and its length (including the
|
||||
// null-terminator).
|
||||
typedef struct {
|
||||
char* buffer;
|
||||
uint32_t length;
|
||||
|
||||
+33
-1
@@ -65,7 +65,15 @@ void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass)
|
||||
subclass->superclass = superclass;
|
||||
|
||||
// Include the superclass in the total number of fields.
|
||||
subclass->numFields += superclass->numFields;
|
||||
if (subclass->numFields != -1)
|
||||
{
|
||||
subclass->numFields += superclass->numFields;
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT(superclass->numFields == 0,
|
||||
"A foreign class cannot inherit from a class with fields.");
|
||||
}
|
||||
|
||||
// Inherit methods from its superclass.
|
||||
for (int i = 0; i < superclass->methods.count; i++)
|
||||
@@ -166,6 +174,16 @@ void wrenResetFiber(WrenVM* vm, ObjFiber* fiber, Obj* fn)
|
||||
wrenAppendCallFrame(vm, fiber, fn, fiber->stack);
|
||||
}
|
||||
|
||||
ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size)
|
||||
{
|
||||
ObjForeign* object = ALLOCATE_FLEX(vm, ObjForeign, uint8_t, size);
|
||||
initObj(vm, &object->obj, OBJ_FOREIGN, classObj);
|
||||
|
||||
// Zero out the bytes.
|
||||
memset(object->data, 0, size);
|
||||
return object;
|
||||
}
|
||||
|
||||
ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module,
|
||||
const Value* constants, int numConstants,
|
||||
int numUpvalues, int arity,
|
||||
@@ -903,6 +921,15 @@ static void markFn(WrenVM* vm, ObjFn* fn)
|
||||
// TODO: What about the function name?
|
||||
}
|
||||
|
||||
static void markForeign(WrenVM* vm, ObjForeign* foreign)
|
||||
{
|
||||
// TODO: Keep track of how much memory the foreign object uses. We can store
|
||||
// this in each foreign object, but it will balloon the size. We may not want
|
||||
// that much overhead. One option would be to let the foreign class register
|
||||
// a C function that returns a size for the object. That way the VM doesn't
|
||||
// always have to explicitly store it.
|
||||
}
|
||||
|
||||
static void markInstance(WrenVM* vm, ObjInstance* instance)
|
||||
{
|
||||
wrenMarkObj(vm, (Obj*)instance->obj.classObj);
|
||||
@@ -1007,6 +1034,7 @@ void wrenMarkObj(WrenVM* vm, Obj* obj)
|
||||
case OBJ_CLOSURE: markClosure( vm, (ObjClosure*) obj); break;
|
||||
case OBJ_FIBER: markFiber( vm, (ObjFiber*) obj); break;
|
||||
case OBJ_FN: markFn( vm, (ObjFn*) obj); break;
|
||||
case OBJ_FOREIGN: markForeign( vm, (ObjForeign*) obj); break;
|
||||
case OBJ_INSTANCE: markInstance(vm, (ObjInstance*)obj); break;
|
||||
case OBJ_LIST: markList( vm, (ObjList*) obj); break;
|
||||
case OBJ_MAP: markMap( vm, (ObjMap*) obj); break;
|
||||
@@ -1059,6 +1087,10 @@ void wrenFreeObj(WrenVM* vm, Obj* obj)
|
||||
DEALLOCATE(vm, fn->debug);
|
||||
break;
|
||||
}
|
||||
|
||||
case OBJ_FOREIGN:
|
||||
// TODO: Call finalizer.
|
||||
break;
|
||||
|
||||
case OBJ_LIST:
|
||||
wrenValueBufferClear(vm, &((ObjList*)obj)->elements);
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
#define AS_CLOSURE(value) ((ObjClosure*)AS_OBJ(value)) // ObjClosure*
|
||||
#define AS_FIBER(v) ((ObjFiber*)AS_OBJ(v)) // ObjFiber*
|
||||
#define AS_FN(value) ((ObjFn*)AS_OBJ(value)) // ObjFn*
|
||||
#define AS_FOREIGN(v) ((ObjForeign*)AS_OBJ(v)) // ObjForeign*
|
||||
#define AS_INSTANCE(value) ((ObjInstance*)AS_OBJ(value)) // ObjInstance*
|
||||
#define AS_LIST(value) ((ObjList*)AS_OBJ(value)) // ObjList*
|
||||
#define AS_MAP(value) ((ObjMap*)AS_OBJ(value)) // ObjMap*
|
||||
@@ -74,6 +75,7 @@
|
||||
#define IS_CLOSURE(value) (wrenIsObjType(value, OBJ_CLOSURE)) // ObjClosure
|
||||
#define IS_FIBER(value) (wrenIsObjType(value, OBJ_FIBER)) // ObjFiber
|
||||
#define IS_FN(value) (wrenIsObjType(value, OBJ_FN)) // ObjFn
|
||||
#define IS_FOREIGN(value) (wrenIsObjType(value, OBJ_FOREIGN)) // ObjForeign
|
||||
#define IS_INSTANCE(value) (wrenIsObjType(value, OBJ_INSTANCE)) // ObjInstance
|
||||
#define IS_RANGE(value) (wrenIsObjType(value, OBJ_RANGE)) // ObjRange
|
||||
#define IS_STRING(value) (wrenIsObjType(value, OBJ_STRING)) // ObjString
|
||||
@@ -89,6 +91,7 @@ typedef enum {
|
||||
OBJ_CLOSURE,
|
||||
OBJ_FIBER,
|
||||
OBJ_FN,
|
||||
OBJ_FOREIGN,
|
||||
OBJ_INSTANCE,
|
||||
OBJ_LIST,
|
||||
OBJ_MAP,
|
||||
@@ -397,6 +400,12 @@ struct sObjClass
|
||||
ObjString* name;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Obj obj;
|
||||
uint8_t data[FLEXIBLE_ARRAY];
|
||||
} ObjForeign;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Obj obj;
|
||||
@@ -652,6 +661,8 @@ static inline void wrenAppendCallFrame(WrenVM* vm, ObjFiber* fiber,
|
||||
frame->ip = wrenGetFrameFunction(frame)->bytecode;
|
||||
}
|
||||
|
||||
ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size);
|
||||
|
||||
// TODO: The argument list here is getting a bit gratuitous.
|
||||
// Creates a new function object with the given code and constants. The new
|
||||
// function will take over ownership of [bytecode] and [sourceLines]. It will
|
||||
|
||||
+194
-72
@@ -37,6 +37,17 @@ static void* defaultReallocate(void* ptr, size_t newSize)
|
||||
return realloc(ptr, newSize);
|
||||
}
|
||||
|
||||
void wrenInitConfiguration(WrenConfiguration* configuration)
|
||||
{
|
||||
configuration->reallocateFn = NULL;
|
||||
configuration->loadModuleFn = NULL;
|
||||
configuration->bindForeignMethodFn = NULL;
|
||||
configuration->bindForeignClassFn = NULL;
|
||||
configuration->initialHeapSize = 1024 * 1024 * 10;
|
||||
configuration->minHeapSize = 1024 * 1024;
|
||||
configuration->heapGrowthPercent = 50;
|
||||
}
|
||||
|
||||
WrenVM* wrenNewVM(WrenConfiguration* configuration)
|
||||
{
|
||||
WrenReallocateFn reallocate = defaultReallocate;
|
||||
@@ -49,32 +60,19 @@ WrenVM* wrenNewVM(WrenConfiguration* configuration)
|
||||
memset(vm, 0, sizeof(WrenVM));
|
||||
|
||||
vm->reallocate = reallocate;
|
||||
vm->bindForeign = configuration->bindForeignMethodFn;
|
||||
vm->bindForeignMethod = configuration->bindForeignMethodFn;
|
||||
vm->bindForeignClass = configuration->bindForeignClassFn;
|
||||
vm->loadModule = configuration->loadModuleFn;
|
||||
vm->nextGC = configuration->initialHeapSize;
|
||||
vm->minNextGC = configuration->minHeapSize;
|
||||
|
||||
// +100 here because the configuration gives us the *additional* size of
|
||||
// the heap relative to the in-use memory, while heapScalePercent is the
|
||||
// *total* size of the heap relative to in-use.
|
||||
vm->heapScalePercent = 100 + configuration->heapGrowthPercent;
|
||||
|
||||
wrenSymbolTableInit(&vm->methodNames);
|
||||
|
||||
vm->nextGC = 1024 * 1024 * 10;
|
||||
if (configuration->initialHeapSize != 0)
|
||||
{
|
||||
vm->nextGC = configuration->initialHeapSize;
|
||||
}
|
||||
|
||||
vm->minNextGC = 1024 * 1024;
|
||||
if (configuration->minHeapSize != 0)
|
||||
{
|
||||
vm->minNextGC = configuration->minHeapSize;
|
||||
}
|
||||
|
||||
vm->heapScalePercent = 150;
|
||||
if (configuration->heapGrowthPercent != 0)
|
||||
{
|
||||
// +100 here because the configuration gives us the *additional* size of
|
||||
// the heap relative to the in-use memory, while heapScalePercent is the
|
||||
// *total* size of the heap relative to in-use.
|
||||
vm->heapScalePercent = 100 + configuration->heapGrowthPercent;
|
||||
}
|
||||
|
||||
ObjString* name = AS_STRING(CONST_STRING(vm, "core"));
|
||||
wrenPushRoot(vm, (Obj*)name);
|
||||
|
||||
@@ -311,24 +309,21 @@ static WrenForeignMethodFn findForeignMethod(WrenVM* vm,
|
||||
{
|
||||
WrenForeignMethodFn fn;
|
||||
|
||||
// Let the host try to find it first.
|
||||
if (vm->bindForeign != NULL)
|
||||
{
|
||||
fn = vm->bindForeign(vm, moduleName, className, isStatic, signature);
|
||||
if (fn != NULL) return fn;
|
||||
}
|
||||
|
||||
// Otherwise, try the built-in libraries.
|
||||
// Bind foreign methods in the core module.
|
||||
if (strcmp(moduleName, "core") == 0)
|
||||
{
|
||||
#if WREN_USE_LIB_IO
|
||||
fn = wrenBindIOForeignMethod(vm, className, signature);
|
||||
if (fn != NULL) return fn;
|
||||
#endif
|
||||
|
||||
ASSERT(fn != NULL, "Failed to bind core module foreign method.");
|
||||
return fn;
|
||||
}
|
||||
|
||||
// TODO: Report a runtime error on failure to find it.
|
||||
return NULL;
|
||||
// For other modules, let the host bind it.
|
||||
if (vm->bindForeignMethod == NULL) return NULL;
|
||||
|
||||
return vm->bindForeignMethod(vm, moduleName, className, isStatic, signature);
|
||||
}
|
||||
|
||||
// Defines [methodValue] as a method on [classObj].
|
||||
@@ -546,18 +541,23 @@ static bool importVariable(WrenVM* vm, Value moduleName, Value variableName,
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verifies that [superclass] is a valid object to inherit from. That means it
|
||||
// must be a class and cannot be the class of any built-in type.
|
||||
// Verifies that [superclassValue] is a valid object to inherit from. That
|
||||
// means it must be a class and cannot be the class of any built-in type.
|
||||
//
|
||||
// If successful, returns null. Otherwise, returns a string for the runtime
|
||||
// Also validates that it doesn't result in a class with too many fields and
|
||||
// the other limitations foreign classes have.
|
||||
//
|
||||
// If successful, returns `null`. Otherwise, returns a string for the runtime
|
||||
// error message.
|
||||
static Value validateSuperclass(WrenVM* vm, Value name,
|
||||
Value superclassValue)
|
||||
static Value validateSuperclass(WrenVM* vm, Value name, Value superclassValue,
|
||||
int numFields)
|
||||
{
|
||||
// Make sure the superclass is a class.
|
||||
if (!IS_CLASS(superclassValue))
|
||||
{
|
||||
return CONST_STRING(vm, "Must inherit from a class.");
|
||||
return wrenStringFormat(vm,
|
||||
"Class '@' cannot inherit from a non-class object.",
|
||||
name);
|
||||
}
|
||||
|
||||
// Make sure it doesn't inherit from a sealed built-in type. Primitive methods
|
||||
@@ -572,13 +572,125 @@ static Value validateSuperclass(WrenVM* vm, Value name,
|
||||
superclass == vm->rangeClass ||
|
||||
superclass == vm->stringClass)
|
||||
{
|
||||
return wrenStringFormat(vm, "@ cannot inherit from @.",
|
||||
return wrenStringFormat(vm,
|
||||
"Class '@' cannot inherit from built-in class '@'.",
|
||||
name, OBJ_VAL(superclass->name));
|
||||
}
|
||||
|
||||
if (superclass->numFields == -1)
|
||||
{
|
||||
return wrenStringFormat(vm,
|
||||
"Class '@' cannot inherit from foreign class '@'.",
|
||||
name, OBJ_VAL(superclass->name));
|
||||
}
|
||||
|
||||
if (numFields == -1 && superclass->numFields > 0)
|
||||
{
|
||||
return wrenStringFormat(vm,
|
||||
"Foreign class '@' may not inherit from a class with fields.",
|
||||
name);
|
||||
}
|
||||
|
||||
if (superclass->numFields + numFields > MAX_FIELDS)
|
||||
{
|
||||
return wrenStringFormat(vm,
|
||||
"Class '@' may not have more than 255 fields, including inherited "
|
||||
"ones.", name);
|
||||
}
|
||||
|
||||
return NULL_VAL;
|
||||
}
|
||||
|
||||
static void bindForeignClass(WrenVM* vm, ObjClass* classObj, ObjModule* module)
|
||||
{
|
||||
// TODO: Make this a runtime error?
|
||||
ASSERT(vm->bindForeignClass != NULL,
|
||||
"Cannot declare foreign classes without a bindForeignClassFn.");
|
||||
|
||||
WrenForeignClassMethods methods = vm->bindForeignClass(
|
||||
vm, module->name->value, classObj->name->value);
|
||||
|
||||
Method method;
|
||||
method.type = METHOD_FOREIGN;
|
||||
method.fn.foreign = methods.allocate;
|
||||
|
||||
ASSERT(method.fn.foreign != NULL,
|
||||
"A foreign class must provide an allocate function.");
|
||||
|
||||
int symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "<allocate>", 10);
|
||||
wrenBindMethod(vm, classObj, symbol, method);
|
||||
|
||||
if (methods.finalize != NULL)
|
||||
{
|
||||
method.fn.foreign = methods.finalize;
|
||||
symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "<finalize>", 10);
|
||||
wrenBindMethod(vm, classObj, symbol, method);
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a new class.
|
||||
//
|
||||
// If [numFields] is -1, the class is a foreign class. The name and superclass
|
||||
// should be on top of the fiber's stack. After calling this, the top of the
|
||||
// stack will contain either the new class or a string if a runtime error
|
||||
// occurred.
|
||||
//
|
||||
// Returns false if the result is an error.
|
||||
static bool defineClass(WrenVM* vm, ObjFiber* fiber, int numFields,
|
||||
ObjModule* module)
|
||||
{
|
||||
// Pull the name and superclass off the stack.
|
||||
Value name = fiber->stackTop[-2];
|
||||
Value superclassValue = fiber->stackTop[-1];
|
||||
|
||||
// We have two values on the stack and we are going to leave one, so discard
|
||||
// the other slot.
|
||||
fiber->stackTop--;
|
||||
|
||||
// Use implicit Object superclass if none given.
|
||||
ObjClass* superclass = vm->objectClass;
|
||||
|
||||
if (!IS_NULL(superclassValue))
|
||||
{
|
||||
Value error = validateSuperclass(vm, name, superclassValue, numFields);
|
||||
if (!IS_NULL(error))
|
||||
{
|
||||
fiber->stackTop[-1] = error;
|
||||
return false;
|
||||
}
|
||||
superclass = AS_CLASS(superclassValue);
|
||||
}
|
||||
|
||||
ObjClass* classObj = wrenNewClass(vm, superclass, numFields, AS_STRING(name));
|
||||
fiber->stackTop[-1] = OBJ_VAL(classObj);
|
||||
|
||||
if (numFields == -1) bindForeignClass(vm, classObj, module);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void createForeign(WrenVM* vm, ObjFiber* fiber, Value* stack)
|
||||
{
|
||||
ObjClass* classObj = AS_CLASS(stack[0]);
|
||||
ASSERT(classObj->numFields == -1, "Class must be a foreign class.");
|
||||
|
||||
// TODO: Don't look up every time.
|
||||
int symbol = wrenSymbolTableFind(&vm->methodNames, "<allocate>", 10);
|
||||
ASSERT(symbol != -1, "Should have defined <allocate> symbol.");
|
||||
|
||||
ASSERT(classObj->methods.count > symbol, "Class should have allocator.");
|
||||
Method* method = &classObj->methods.data[symbol];
|
||||
ASSERT(method->type == METHOD_FOREIGN, "Allocator should be foreign.");
|
||||
|
||||
// Pass the constructor arguments to the allocator as well.
|
||||
vm->foreignCallSlot = stack;
|
||||
vm->foreignCallNumArgs = (int)(fiber->stackTop - stack);
|
||||
|
||||
method->fn.foreign(vm);
|
||||
|
||||
// TODO: Check that allocateForeign was called.
|
||||
}
|
||||
|
||||
// The main bytecode interpreter loop. This is where the magic happens. It is
|
||||
// also, as you can imagine, highly performance critical. Returns `true` if the
|
||||
// fiber completed without error.
|
||||
@@ -1022,6 +1134,11 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
|
||||
stackStart[0] = wrenNewInstance(vm, AS_CLASS(stackStart[0]));
|
||||
DISPATCH();
|
||||
|
||||
CASE_CODE(FOREIGN_CONSTRUCT):
|
||||
ASSERT(IS_CLASS(stackStart[0]), "'this' should be a class.");
|
||||
createForeign(vm, fiber, stackStart);
|
||||
DISPATCH();
|
||||
|
||||
CASE_CODE(CLOSURE):
|
||||
{
|
||||
ObjFn* prototype = AS_FN(fn->constants[READ_SHORT()]);
|
||||
@@ -1057,40 +1174,16 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
|
||||
|
||||
CASE_CODE(CLASS):
|
||||
{
|
||||
Value name = PEEK2();
|
||||
ObjClass* superclass = vm->objectClass;
|
||||
|
||||
// Use implicit Object superclass if none given.
|
||||
if (!IS_NULL(PEEK()))
|
||||
{
|
||||
Value error = validateSuperclass(vm, name, PEEK());
|
||||
if (!IS_NULL(error)) RUNTIME_ERROR(error);
|
||||
superclass = AS_CLASS(PEEK());
|
||||
}
|
||||
|
||||
int numFields = READ_BYTE();
|
||||
|
||||
Value classObj = OBJ_VAL(wrenNewClass(vm, superclass, numFields,
|
||||
AS_STRING(name)));
|
||||
|
||||
// Don't pop the superclass and name off the stack until the subclass is
|
||||
// done being created, to make sure it doesn't get collected.
|
||||
DROP();
|
||||
DROP();
|
||||
|
||||
// Now that we know the total number of fields, make sure we don't
|
||||
// overflow.
|
||||
if (superclass->numFields + numFields > MAX_FIELDS)
|
||||
{
|
||||
RUNTIME_ERROR(wrenStringFormat(vm,
|
||||
"Class '@' may not have more than 255 fields, including inherited "
|
||||
"ones.", name));
|
||||
}
|
||||
|
||||
PUSH(classObj);
|
||||
if (!defineClass(vm, fiber, READ_BYTE(), NULL)) RUNTIME_ERROR(PEEK());
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
|
||||
CASE_CODE(FOREIGN_CLASS):
|
||||
{
|
||||
if (!defineClass(vm, fiber, -1, fn->module)) RUNTIME_ERROR(PEEK());
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
CASE_CODE(METHOD_INSTANCE):
|
||||
CASE_CODE(METHOD_STATIC):
|
||||
{
|
||||
@@ -1323,6 +1416,20 @@ void wrenReleaseValue(WrenVM* vm, WrenValue* value)
|
||||
DEALLOCATE(vm, value);
|
||||
}
|
||||
|
||||
void* wrenAllocateForeign(WrenVM* vm, size_t size)
|
||||
{
|
||||
ASSERT(vm->foreignCallSlot != NULL, "Must be in foreign call.");
|
||||
|
||||
// TODO: Validate this. It can fail if the user calls this inside another
|
||||
// foreign method, or calls one of the return functions.
|
||||
ObjClass* classObj = AS_CLASS(vm->foreignCallSlot[0]);
|
||||
|
||||
ObjForeign* foreign = wrenNewForeign(vm, classObj, size);
|
||||
vm->foreignCallSlot[0] = OBJ_VAL(foreign);
|
||||
|
||||
return (void*)foreign->data;
|
||||
}
|
||||
|
||||
// Execute [source] in the context of the core module.
|
||||
static WrenInterpretResult loadIntoCore(WrenVM* vm, const char* source)
|
||||
{
|
||||
@@ -1460,6 +1567,12 @@ void wrenPopRoot(WrenVM* vm)
|
||||
vm->numTempRoots--;
|
||||
}
|
||||
|
||||
int wrenGetArgumentCount(WrenVM* vm)
|
||||
{
|
||||
ASSERT(vm->foreignCallSlot != NULL, "Must be in foreign call.");
|
||||
return vm->foreignCallNumArgs;
|
||||
}
|
||||
|
||||
static void validateForeignArgument(WrenVM* vm, int index)
|
||||
{
|
||||
ASSERT(vm->foreignCallSlot != NULL, "Must be in foreign call.");
|
||||
@@ -1485,6 +1598,15 @@ double wrenGetArgumentDouble(WrenVM* vm, int index)
|
||||
return AS_NUM(*(vm->foreignCallSlot + index));
|
||||
}
|
||||
|
||||
void* wrenGetArgumentForeign(WrenVM* vm, int index)
|
||||
{
|
||||
validateForeignArgument(vm, index);
|
||||
|
||||
if (!IS_FOREIGN(*(vm->foreignCallSlot + index))) return NULL;
|
||||
|
||||
return AS_FOREIGN(*(vm->foreignCallSlot + index))->data;
|
||||
}
|
||||
|
||||
const char* wrenGetArgumentString(WrenVM* vm, int index)
|
||||
{
|
||||
validateForeignArgument(vm, index);
|
||||
|
||||
+4
-1
@@ -125,7 +125,10 @@ struct WrenVM
|
||||
int foreignCallNumArgs;
|
||||
|
||||
// The function used to locate foreign functions.
|
||||
WrenBindForeignMethodFn bindForeign;
|
||||
WrenBindForeignMethodFn bindForeignMethod;
|
||||
|
||||
// The function used to locate foreign classes.
|
||||
WrenBindForeignClassFn bindForeignClass;
|
||||
|
||||
// The function used to load modules.
|
||||
WrenLoadModuleFn loadModule;
|
||||
|
||||
Reference in New Issue
Block a user