Make constructors just methods.

* Eliminate "new" reserved word.
* Allow "this" before a method definition to define a constructor.
* Only create a default constructor for classes that don't define one.
This commit is contained in:
Bob Nystrom
2015-07-10 09:18:22 -07:00
parent 0ddaa2517c
commit 5fb6186d7d
221 changed files with 864 additions and 654 deletions
+7 -6
View File
@@ -95,14 +95,15 @@
// The maximum length of a method signature. Signatures look like:
//
// foo // Getter.
// foo() // No-argument method.
// foo(_) // One-argument method.
// foo(_,_) // Two-argument method.
// foo // Getter.
// foo() // No-argument method.
// foo(_) // One-argument method.
// foo(_,_) // Two-argument method.
// this foo() // Constructor initializer.
//
// The maximum signature length takes into account the longest method name, the
// maximum number of parameters with separators between them, and "()".
#define MAX_METHOD_SIGNATURE (MAX_METHOD_NAME + (MAX_PARAMETERS * 2) + 1)
// maximum number of parameters with separators between them, "this ", 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
// is so that error messages mentioning variables can be stack allocated.
+249 -140
View File
@@ -81,7 +81,6 @@ typedef enum
TOKEN_IMPORT,
TOKEN_IN,
TOKEN_IS,
TOKEN_NEW,
TOKEN_NULL,
TOKEN_RETURN,
TOKEN_STATIC,
@@ -213,6 +212,39 @@ typedef struct sLoop
struct sLoop* enclosing;
} Loop;
// The different signature syntaxes for different kinds of methods.
typedef enum
{
// A name followed by a (possibly empty) parenthesized parameter list. Also
// used for binary operators.
SIG_METHOD,
// Just a name. Also used for unary operators.
SIG_GETTER,
// A name followed by "=".
SIG_SETTER,
// A square bracketed parameter list.
SIG_SUBSCRIPT,
// A square bracketed parameter list followed by "=".
SIG_SUBSCRIPT_SETTER,
// A constructor initializer function. This has a distinct signature to
// prevent it from being invoked directly outside of the constructor on the
// metaclass.
SIG_INITIALIZER
} SignatureType;
typedef struct
{
const char* name;
int length;
SignatureType type;
int arity;
} Signature;
// Bookkeeping information for compiling a class definition.
typedef struct
{
@@ -220,14 +252,10 @@ typedef struct
SymbolTable* fields;
// True if the current method being compiled is static.
bool isStaticMethod;
bool inStatic;
// The name of the method being compiled. Note that this is just the bare
// method name, and not its full signature.
const char* methodName;
// The length of the method name being compiled.
int methodLength;
// The signature of the method being compiled.
Signature* signature;
} ClassCompiler;
struct sCompiler
@@ -608,7 +636,6 @@ static void readName(Parser* parser, TokenType type)
else if (isKeyword(parser, "import")) type = TOKEN_IMPORT;
else if (isKeyword(parser, "in")) type = TOKEN_IN;
else if (isKeyword(parser, "is")) type = TOKEN_IS;
else if (isKeyword(parser, "new")) type = TOKEN_NEW;
else if (isKeyword(parser, "null")) type = TOKEN_NULL;
else if (isKeyword(parser, "return")) type = TOKEN_RETURN;
else if (isKeyword(parser, "static")) type = TOKEN_STATIC;
@@ -1386,34 +1413,6 @@ typedef enum
typedef void (*GrammarFn)(Compiler*, bool allowAssignment);
// The different signature syntaxes for different kinds of methods.
typedef enum
{
// A name followed by a (possibly empty) parenthesized parameter list. Also
// used for binary operators.
SIG_METHOD,
// Just a name. Also used for unary operators.
SIG_GETTER,
// A name followed by "=".
SIG_SETTER,
// A square bracketed parameter list.
SIG_SUBSCRIPT,
// A square bracketed parameter list followed by "=".
SIG_SUBSCRIPT_SETTER
} SignatureType;
typedef struct
{
const char* name;
int length;
SignatureType type;
int arity;
} Signature;
typedef void (*SignatureFn)(Compiler* compiler, Signature* signature);
typedef struct
@@ -1484,13 +1483,16 @@ static bool finishBlock(Compiler* compiler)
}
// Parses a method or function body, after the initial "{" has been consumed.
static void finishBody(Compiler* compiler, bool isConstructor)
//
// It [isInitializer] is `true`, this is the body of a constructor initializer.
// In that case, this adds the code to ensure it returns `this`.
static void finishBody(Compiler* compiler, bool isInitializer)
{
bool isExpressionBody = finishBlock(compiler);
if (isConstructor)
if (isInitializer)
{
// If the constructor body evaluates to a value, discard it.
// If the initializer body evaluates to a value, discard it.
if (isExpressionBody) emit(compiler, CODE_POP);
// The receiver is always stored in the first local slot.
@@ -1559,9 +1561,11 @@ static void signatureParameterList(char name[MAX_METHOD_SIGNATURE], int* length,
static void signatureToString(Signature* signature,
char name[MAX_METHOD_SIGNATURE], int* length)
{
*length = 0;
// Build the full name from the signature.
*length = signature->length;
memcpy(name, signature->name, *length);
memcpy(name + *length, signature->name, signature->length);
*length += signature->length;
switch (signature->type)
{
@@ -1587,6 +1591,13 @@ static void signatureToString(Signature* signature,
name[(*length)++] = '=';
signatureParameterList(name, length, 1, '(', ')');
break;
case SIG_INITIALIZER:
memcpy(name, "this ", 5);
memcpy(name + 5, signature->name, signature->length);
*length = 5 + signature->length;
signatureParameterList(name, length, signature->arity, '(', ')');
break;
}
name[*length] = '\0';
@@ -1608,7 +1619,6 @@ static void signatureFromToken(Compiler* compiler, Signature* signature)
{
// Get the token for the method name.
Token* token = &compiler->parser->previous;
signature->type = SIG_GETTER;
signature->arity = 0;
signature->name = token->start;
signature->length = token->length;
@@ -1667,15 +1677,18 @@ static void callMethod(Compiler* compiler, int numArgs, const char* name,
emitShortArg(compiler, (Code)(CODE_CALL_0 + numArgs), symbol);
}
// Compiles an (optional) argument list and then calls it.
// Compiles an (optional) argument list for a method call with [methodSignature]
// and then calls it.
static void methodCall(Compiler* compiler, Code instruction,
const char* name, int length)
Signature* methodSignature)
{
// Make a new signature that contains the updated arity and type based on
// the arguments we find.
Signature signature;
signature.type = SIG_GETTER;
signature.arity = 0;
signature.name = name;
signature.length = length;
signature.name = methodSignature->name;
signature.length = methodSignature->length;
// Parse the argument list, if any.
if (match(compiler, TOKEN_LEFT_PAREN))
@@ -1726,6 +1739,18 @@ static void methodCall(Compiler* compiler, Code instruction,
// TODO: Allow Grace-style mixfix methods?
// If this is a super() call for an initializer, make sure we got an actual
// argument list.
if (methodSignature->type == SIG_INITIALIZER)
{
if (signature.type != SIG_METHOD)
{
error(compiler, "A superclass constructor must have an argument list.");
}
signature.type = SIG_INITIALIZER;
}
callSignature(compiler, instruction, &signature);
}
@@ -1754,7 +1779,7 @@ static void namedCall(Compiler* compiler, bool allowAssignment,
}
else
{
methodCall(compiler, instruction, signature.name, signature.length);
methodCall(compiler, instruction, &signature);
}
}
@@ -1791,7 +1816,7 @@ static void list(Compiler* compiler, bool allowAssignment)
emitShortArg(compiler, CODE_LOAD_MODULE_VAR, listClassSymbol);
// Instantiate a new list.
callMethod(compiler, 0, "<instantiate>", 13);
callMethod(compiler, 0, "new()", 5);
// Compile the list elements. Each one compiles to a ".add()" call.
if (peek(compiler) != TOKEN_RIGHT_BRACKET)
@@ -1827,7 +1852,7 @@ static void map(Compiler* compiler, bool allowAssignment)
emitShortArg(compiler, CODE_LOAD_MODULE_VAR, mapClassSymbol);
// Instantiate a new map.
callMethod(compiler, 0, "<instantiate>", 13);
callMethod(compiler, 0, "new()", 5);
// Compile the map elements. Each one is compiled to just invoke the
// subscript setter on the map.
@@ -1912,7 +1937,7 @@ static void field(Compiler* compiler, bool allowAssignment)
{
error(compiler, "Cannot reference a field outside of a class definition.");
}
else if (enclosingClass->isStaticMethod)
else if (enclosingClass->inStatic)
{
error(compiler, "Cannot use an instance field in a static method.");
}
@@ -2138,14 +2163,17 @@ static void super_(Compiler* compiler, bool allowAssignment)
{
error(compiler, "Cannot use 'super' outside of a method.");
}
else if (enclosingClass->isStaticMethod)
else if (enclosingClass->inStatic)
{
// TODO: Why not?
error(compiler, "Cannot use 'super' in a static method.");
}
loadThis(compiler);
// TODO: Super operator calls.
// TODO: There's no syntax for invoking a superclass constructor with a
// different name from the enclosing one. Figure that out.
// See if it's a named super call, or an unnamed one.
if (match(compiler, TOKEN_DOT))
@@ -2159,8 +2187,7 @@ static void super_(Compiler* compiler, bool allowAssignment)
// No explicit name, so use the name of the enclosing method. Make sure we
// check that enclosingClass isn't NULL first. We've already reported the
// error, but we don't want to crash here.
methodCall(compiler, CODE_SUPER_0, enclosingClass->methodName,
enclosingClass->methodLength);
methodCall(compiler, CODE_SUPER_0, enclosingClass->signature);
}
}
@@ -2209,23 +2236,6 @@ static void call(Compiler* compiler, bool allowAssignment)
namedCall(compiler, allowAssignment, CODE_CALL_0);
}
static void new_(Compiler* compiler, bool allowAssignment)
{
// Allow a dotted name after 'new'.
consume(compiler, TOKEN_NAME, "Expect name after 'new'.");
name(compiler, false);
while (match(compiler, TOKEN_DOT))
{
call(compiler, false);
}
// The angle brackets in the name are to ensure users can't call it directly.
callMethod(compiler, 0, "<instantiate>", 13);
// Invoke the constructor on the new instance.
methodCall(compiler, CODE_CALL_0, "new", 3);
}
static void and_(Compiler* compiler, bool allowAssignment)
{
ignoreNewlines(compiler);
@@ -2309,7 +2319,6 @@ void infixSignature(Compiler* compiler, Signature* signature)
void unarySignature(Compiler* compiler, Signature* signature)
{
// Do nothing. The name is already complete.
signature->type = SIG_GETTER;
}
// Compiles a method signature for an operator that can either be unary or
@@ -2381,9 +2390,9 @@ static void parameterList(Compiler* compiler, Signature* signature)
{
// The parameter list is optional.
if (!match(compiler, TOKEN_LEFT_PAREN)) return;
signature->type = SIG_METHOD;
// Allow an empty parameter list.
if (match(compiler, TOKEN_RIGHT_PAREN)) return;
@@ -2395,7 +2404,7 @@ static void parameterList(Compiler* compiler, Signature* signature)
void namedSignature(Compiler* compiler, Signature* signature)
{
signature->type = SIG_GETTER;
// If it's a setter, it can't also have a parameter list.
if (maybeSetter(compiler, signature)) return;
@@ -2406,10 +2415,29 @@ void namedSignature(Compiler* compiler, Signature* signature)
// Compiles a method signature for a constructor.
void constructorSignature(Compiler* compiler, Signature* signature)
{
signature->type = SIG_GETTER;
signature->type = SIG_INITIALIZER;
consume(compiler, TOKEN_NAME, "Expect constructor name after 'this'.");
// Capture the name.
signatureFromToken(compiler, signature);
if (match(compiler, TOKEN_EQ))
{
error(compiler, "A constructor cannot be a setter.");
}
// Add the parameters, if there are any.
parameterList(compiler, signature);
if (!match(compiler, TOKEN_LEFT_PAREN))
{
error(compiler, "A constructor cannot be a getter.");
return;
}
// Allow an empty parameter list.
if (match(compiler, TOKEN_RIGHT_PAREN)) return;
finishParameterList(compiler, signature);
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameters.");
}
// This table defines all of the parsing rules for the prefix and infix
@@ -2468,12 +2496,11 @@ GrammarRule rules[] =
/* TOKEN_IMPORT */ UNUSED,
/* TOKEN_IN */ UNUSED,
/* TOKEN_IS */ INFIX_OPERATOR(PREC_IS, "is"),
/* TOKEN_NEW */ { new_, NULL, constructorSignature, PREC_NONE, NULL },
/* TOKEN_NULL */ PREFIX(null),
/* TOKEN_RETURN */ UNUSED,
/* TOKEN_STATIC */ UNUSED,
/* TOKEN_SUPER */ PREFIX(super_),
/* TOKEN_THIS */ PREFIX(this_),
/* TOKEN_THIS */ { this_, NULL, constructorSignature, PREC_NONE, NULL },
/* TOKEN_TRUE */ PREFIX(boolean),
/* TOKEN_VAR */ UNUSED,
/* TOKEN_WHILE */ UNUSED,
@@ -2570,6 +2597,7 @@ static int getNumArguments(const uint8_t* bytecode, const Value* constants,
case CODE_LOAD_LOCAL_6:
case CODE_LOAD_LOCAL_7:
case CODE_LOAD_LOCAL_8:
case CODE_CONSTRUCT:
return 0;
case CODE_LOAD_LOCAL:
@@ -2904,24 +2932,103 @@ void statement(Compiler* compiler)
emit(compiler, CODE_POP);
}
// Compiles a method definition inside a class body. Returns the symbol in the
// method table for the new method.
static int method(Compiler* compiler, ClassCompiler* classCompiler,
bool isConstructor, bool isForeign, SignatureFn signatureFn)
// Creates a matching constructor method for an initializer with [signature]
// and [initializerSymbol].
//
// Construction is a two-stage process in Wren that involves two separate
// methods. There is a static method that allocates a new instance of the class.
// It then invokes an initializer method on the new instance, forwarding all of
// the constructor arguments to it.
//
// The allocator method always has a fixed implementation:
//
// CODE_CONSTRUCT - Replace the class in slot 0 with a new instance of it.
// CODE_CALL - Invoke the initializer on the new instance.
//
// This creates that method and calls the initializer with [initializerSymbol].
static void createConstructor(Compiler* compiler, Signature* signature,
int initializerSymbol)
{
// Build the method signature.
Signature signature;
signatureFromToken(compiler, &signature);
Compiler methodCompiler;
initCompiler(&methodCompiler, compiler->parser, compiler, false);
// Allocate the instance.
emit(&methodCompiler, CODE_CONSTRUCT);
// Run its initializer.
emitShortArg(&methodCompiler, CODE_CALL_0 + signature->arity,
initializerSymbol);
// Return the instance.
emit(&methodCompiler, CODE_RETURN);
endCompiler(&methodCompiler, "", 0);
}
classCompiler->methodName = signature.name;
classCompiler->methodLength = signature.length;
// Loads the enclosing class onto the stack and then binds the function already
// on the stack as a method on that class.
static void defineMethod(Compiler* compiler, int classSlot, bool isStatic,
int methodSymbol)
{
// Load the class. We have to do this for each method because we can't
// keep the class on top of the stack. If there are static fields, they
// will be locals above the initial variable slot for the class on the
// stack. To skip past those, we just load the class each time right before
// defining a method.
if (compiler->scopeDepth == 0)
{
// The class is at the top level (scope depth is 0, not -1 to account for
// the static variable scope surrounding the class itself), so load it from
// there.
emitShortArg(compiler, CODE_LOAD_MODULE_VAR, classSlot);
}
else
{
loadLocal(compiler, classSlot);
}
// Define the method.
Code instruction = isStatic ? CODE_METHOD_STATIC : CODE_METHOD_INSTANCE;
emitShortArg(compiler, instruction, methodSymbol);
}
// Compiles a method definition inside a class body.
//
// Returns `true` if it compiled successfully, or `false` if the method couldn't
// be parsed.
static bool method(Compiler* compiler, ClassCompiler* classCompiler,
int classSlot, bool* hasConstructor)
{
Signature signature;
classCompiler->signature = &signature;
// TODO: What about foreign constructors?
bool isForeign = match(compiler, TOKEN_FOREIGN);
classCompiler->inStatic = match(compiler, TOKEN_STATIC);
SignatureFn signatureFn = rules[compiler->parser->current.type].method;
nextToken(compiler->parser);
if (signatureFn == NULL)
{
error(compiler, "Expect method definition.");
return false;
}
// Build the method signature.
signatureFromToken(compiler, &signature);
Compiler methodCompiler;
initCompiler(&methodCompiler, compiler->parser, compiler, false);
// Compile the method signature.
signatureFn(&methodCompiler, &signature);
if (classCompiler->inStatic && signature.type == SIG_INITIALIZER)
{
error(compiler, "A constructor cannot be static.");
}
// Include the full signature in debug messages in stack traces.
char fullSignature[MAX_METHOD_SIGNATURE];
int length;
@@ -2941,12 +3048,52 @@ static int method(Compiler* compiler, ClassCompiler* classCompiler,
else
{
consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' to begin method body.");
finishBody(&methodCompiler, isConstructor);
finishBody(&methodCompiler, signature.type == SIG_INITIALIZER);
endCompiler(&methodCompiler, fullSignature, length);
}
// Define the method. For a constructor, this defines the instance
// initializer method.
int methodSymbol = signatureSymbol(compiler, &signature);
defineMethod(compiler, classSlot, classCompiler->inStatic, methodSymbol);
return signatureSymbol(compiler, &signature);
if (signature.type == SIG_INITIALIZER)
{
// Also define a matching constructor method on the metaclass.
signature.type = SIG_METHOD;
int constructorSymbol = signatureSymbol(compiler, &signature);
createConstructor(compiler, &signature, methodSymbol);
defineMethod(compiler, classSlot, true, constructorSymbol);
// We don't need a default constructor anymore.
*hasConstructor = true;
}
return true;
}
// Defines a default "new()" constructor on the current class.
//
// It just invokes "this 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;
signature.name = "new";
signature.length = 3;
signature.type = SIG_INITIALIZER;
signature.arity = 0;
int initializerSymbol = signatureSymbol(compiler, &signature);
signature.type = SIG_METHOD;
int constructorSymbol = signatureSymbol(compiler, &signature);
createConstructor(compiler, &signature, initializerSymbol);
defineMethod(compiler, classSlot, true, constructorSymbol);
}
// Compiles a class definition. Assumes the "class" token has already been
@@ -2955,7 +3102,6 @@ static void classDefinition(Compiler* compiler)
{
// Create a variable to store the class in.
int slot = declareNamedVariable(compiler);
bool isModule = compiler->scopeDepth == -1;
// Make a string constant for the name.
int nameConstant = addConstant(compiler, wrenNewString(compiler->parser->vm,
@@ -3004,60 +3150,23 @@ static void classDefinition(Compiler* compiler)
consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' after class declaration.");
matchLine(compiler);
bool hasConstructor = false;
while (!match(compiler, TOKEN_RIGHT_BRACE))
{
Code instruction = CODE_METHOD_INSTANCE;
bool isConstructor = false;
// TODO: What about foreign constructors?
bool isForeign = match(compiler, TOKEN_FOREIGN);
classCompiler.isStaticMethod = false;
if (match(compiler, TOKEN_STATIC))
{
instruction = CODE_METHOD_STATIC;
classCompiler.isStaticMethod = true;
}
else if (peek(compiler) == TOKEN_NEW)
{
// If the method name is "new", it's a constructor.
isConstructor = true;
}
SignatureFn signature = rules[compiler->parser->current.type].method;
nextToken(compiler->parser);
if (signature == NULL)
{
error(compiler, "Expect method definition.");
break;
}
int methodSymbol = method(compiler, &classCompiler, isConstructor,
isForeign, signature);
// Load the class. We have to do this for each method because we can't
// keep the class on top of the stack. If there are static fields, they
// will be locals above the initial variable slot for the class on the
// stack. To skip past those, we just load the class each time right before
// defining a method.
if (isModule)
{
emitShortArg(compiler, CODE_LOAD_MODULE_VAR, slot);
}
else
{
loadLocal(compiler, slot);
}
// Define the method.
emitShortArg(compiler, instruction, methodSymbol);
if (!method(compiler, &classCompiler, slot, &hasConstructor)) break;
// Don't require a newline after the last definition.
if (match(compiler, TOKEN_RIGHT_BRACE)) break;
consumeLine(compiler, "Expect newline after definition in class.");
}
// If no constructor was defined, create a default new() one.
if (!hasConstructor)
{
createDefaultConstructor(compiler, slot);
}
// Update the class with the number of fields.
compiler->bytecode.data[numFieldsInstruction] = (uint8_t)fields.count;
+18 -36
View File
@@ -68,9 +68,9 @@ static const char* coreLibSource =
"\n"
" isEmpty { iterate(null) ? false : true }\n"
"\n"
" map(transformation) { new MapSequence(this, transformation) }\n"
" map(transformation) { MapSequence.new(this, transformation) }\n"
"\n"
" where(predicate) { new WhereSequence(this, predicate) }\n"
" where(predicate) { WhereSequence.new(this, predicate) }\n"
"\n"
" reduce(acc, f) {\n"
" for (element in this) {\n"
@@ -108,7 +108,7 @@ static const char* coreLibSource =
" }\n"
"\n"
" toList {\n"
" var result = new List\n"
" var result = List.new()\n"
" for (element in this) {\n"
" result.add(element)\n"
" }\n"
@@ -117,7 +117,7 @@ static const char* coreLibSource =
"}\n"
"\n"
"class MapSequence is Sequence {\n"
" new(sequence, fn) {\n"
" this new(sequence, fn) {\n"
" _sequence = sequence\n"
" _fn = fn\n"
" }\n"
@@ -127,7 +127,7 @@ static const char* coreLibSource =
"}\n"
"\n"
"class WhereSequence is Sequence {\n"
" new(sequence, fn) {\n"
" this new(sequence, fn) {\n"
" _sequence = sequence\n"
" _fn = fn\n"
" }\n"
@@ -143,11 +143,11 @@ static const char* coreLibSource =
"}\n"
"\n"
"class String is Sequence {\n"
" bytes { new StringByteSequence(this) }\n"
" bytes { StringByteSequence.new(this) }\n"
"}\n"
"\n"
"class StringByteSequence is Sequence {\n"
" new(string) {\n"
" this new(string) {\n"
" _string = string\n"
" }\n"
"\n"
@@ -176,8 +176,8 @@ static const char* coreLibSource =
"}\n"
"\n"
"class Map {\n"
" keys { new MapKeySequence(this) }\n"
" values { new MapValueSequence(this) }\n"
" keys { MapKeySequence.new(this) }\n"
" values { MapValueSequence.new(this) }\n"
"\n"
" toString {\n"
" var first = true\n"
@@ -194,7 +194,7 @@ static const char* coreLibSource =
"}\n"
"\n"
"class MapKeySequence is Sequence {\n"
" new(map) {\n"
" this new(map) {\n"
" _map = map\n"
" }\n"
"\n"
@@ -203,7 +203,7 @@ static const char* coreLibSource =
"}\n"
"\n"
"class MapValueSequence is Sequence {\n"
" new(map) {\n"
" this new(map) {\n"
" _map = map\n"
" }\n"
"\n"
@@ -215,13 +215,9 @@ static const char* coreLibSource =
// A simple primitive that just returns "this". Used in a few different places:
//
// * The default constructor on Object needs no initialization so just uses
// this.
// * The default new() initializer on Object needs no initialization so just
// uses this.
// * String's toString method obviously can use this.
// * Fiber's instantiate method just returns the Fiber class. The new() method
// is responsible for creating the new fiber.
// * Fn's "constructor" is given the actual function as an argument, so the
// instantiate method just returns the Fn class.
DEF_PRIMITIVE(return_this)
{
RETURN_VAL(args[0]);
@@ -244,11 +240,6 @@ DEF_PRIMITIVE(bool_toString)
}
}
DEF_PRIMITIVE(class_instantiate)
{
RETURN_VAL(wrenNewInstance(vm, AS_CLASS(args[0])));
}
DEF_PRIMITIVE(class_name)
{
RETURN_OBJ(AS_CLASS(args[0])->name);
@@ -532,7 +523,7 @@ DEF_PRIMITIVE(fn_toString)
RETURN_VAL(CONST_STRING(vm, "<fn>"));
}
DEF_PRIMITIVE(list_instantiate)
DEF_PRIMITIVE(list_new)
{
RETURN_OBJ(wrenNewList(vm, 0));
}
@@ -649,7 +640,7 @@ DEF_PRIMITIVE(list_subscriptSetter)
RETURN_VAL(args[2]);
}
DEF_PRIMITIVE(map_instantiate)
DEF_PRIMITIVE(map_new)
{
RETURN_OBJ(wrenNewMap(vm));
}
@@ -998,11 +989,6 @@ DEF_PRIMITIVE(object_type)
RETURN_OBJ(wrenGetClass(vm, args[0]));
}
DEF_PRIMITIVE(object_instantiate)
{
RETURN_ERROR("Must provide a class to 'new' to construct.");
}
DEF_PRIMITIVE(range_from)
{
RETURN_NUM(AS_RANGE(args[0])->from);
@@ -1303,16 +1289,14 @@ void wrenInitializeCore(WrenVM* vm)
PRIMITIVE(vm->objectClass, "!", object_not);
PRIMITIVE(vm->objectClass, "==(_)", object_eqeq);
PRIMITIVE(vm->objectClass, "!=(_)", object_bangeq);
PRIMITIVE(vm->objectClass, "new", return_this);
PRIMITIVE(vm->objectClass, "this new()", return_this);
PRIMITIVE(vm->objectClass, "is(_)", object_is);
PRIMITIVE(vm->objectClass, "toString", object_toString);
PRIMITIVE(vm->objectClass, "type", object_type);
PRIMITIVE(vm->objectClass, "<instantiate>", object_instantiate);
// Now we can define Class, which is a subclass of Object.
vm->classClass = defineClass(vm, "Class");
wrenBindSuperclass(vm, vm->classClass, vm->objectClass);
PRIMITIVE(vm->classClass, "<instantiate>", class_instantiate);
PRIMITIVE(vm->classClass, "name", class_name);
PRIMITIVE(vm->classClass, "supertype", class_supertype);
PRIMITIVE(vm->classClass, "toString", class_toString);
@@ -1361,7 +1345,6 @@ void wrenInitializeCore(WrenVM* vm)
PRIMITIVE(vm->boolClass, "!", bool_not);
vm->fiberClass = AS_CLASS(wrenFindVariable(vm, "Fiber"));
PRIMITIVE(vm->fiberClass->obj.classObj, "<instantiate>", return_this);
PRIMITIVE(vm->fiberClass->obj.classObj, "new(_)", fiber_new);
PRIMITIVE(vm->fiberClass->obj.classObj, "abort(_)", fiber_abort);
PRIMITIVE(vm->fiberClass->obj.classObj, "current", fiber_current);
@@ -1376,7 +1359,6 @@ void wrenInitializeCore(WrenVM* vm)
PRIMITIVE(vm->fiberClass, "try()", fiber_try);
vm->fnClass = AS_CLASS(wrenFindVariable(vm, "Fn"));
PRIMITIVE(vm->fnClass->obj.classObj, "<instantiate>", return_this);
PRIMITIVE(vm->fnClass->obj.classObj, "new(_)", fn_new);
PRIMITIVE(vm->fnClass, "arity", fn_arity);
@@ -1463,7 +1445,7 @@ void wrenInitializeCore(WrenVM* vm)
PRIMITIVE(vm->stringClass, "toString", return_this);
vm->listClass = AS_CLASS(wrenFindVariable(vm, "List"));
PRIMITIVE(vm->listClass->obj.classObj, "<instantiate>", list_instantiate);
PRIMITIVE(vm->listClass->obj.classObj, "new()", list_new);
PRIMITIVE(vm->listClass, "[_]", list_subscript);
PRIMITIVE(vm->listClass, "[_]=(_)", list_subscriptSetter);
PRIMITIVE(vm->listClass, "add(_)", list_add);
@@ -1475,7 +1457,7 @@ void wrenInitializeCore(WrenVM* vm)
PRIMITIVE(vm->listClass, "removeAt(_)", list_removeAt);
vm->mapClass = AS_CLASS(wrenFindVariable(vm, "Map"));
PRIMITIVE(vm->mapClass->obj.classObj, "<instantiate>", map_instantiate);
PRIMITIVE(vm->mapClass->obj.classObj, "new()", map_new);
PRIMITIVE(vm->mapClass, "[_]", map_subscript);
PRIMITIVE(vm->mapClass, "[_]=(_)", map_subscriptSetter);
PRIMITIVE(vm->mapClass, "clear()", map_clear);
+2
View File
@@ -277,6 +277,8 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine)
break;
}
case CODE_CONSTRUCT: printf("CODE_CONSTRUCT\n"); break;
case CODE_CLASS:
{
int numFields = READ_BYTE();
+7
View File
@@ -150,6 +150,13 @@ OPCODE(RETURN)
// Pushes the created closure.
OPCODE(CLOSURE)
// Creates a new instance of a 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(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.
+5
View File
@@ -1023,6 +1023,11 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
DISPATCH();
}
CASE_CODE(CONSTRUCT):
ASSERT(IS_CLASS(stackStart[0]), "'this' should be a class.");
stackStart[0] = wrenNewInstance(vm, AS_CLASS(stackStart[0]));
DISPATCH();
CASE_CODE(CLOSURE):
{
ObjFn* prototype = AS_FN(fn->constants[READ_SHORT()]);