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:
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()]);
|
||||
|
||||
Reference in New Issue
Block a user