#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "wren_common.h"
#include "wren_compiler.h"
#include "wren_vm.h"
#if WREN_DEBUG_DUMP_COMPILED_CODE
#include "wren_debug.h"
#endif
#define MAX_LOCALS 256
#define MAX_UPVALUES 256
#define MAX_CONSTANTS (1 << 16)
#define MAX_JUMP (1 << 16)
#define MAX_INTERPOLATION_NESTING 8
#define ERROR_MESSAGE_SIZE (80 + MAX_VARIABLE_NAME + 15)
typedef enum
{
TOKEN_LEFT_PAREN,
TOKEN_RIGHT_PAREN,
TOKEN_LEFT_BRACKET,
TOKEN_RIGHT_BRACKET,
TOKEN_LEFT_BRACE,
TOKEN_RIGHT_BRACE,
TOKEN_COLON,
TOKEN_DOT,
TOKEN_DOTDOT,
TOKEN_DOTDOTDOT,
TOKEN_COMMA,
TOKEN_STAR,
TOKEN_SLASH,
TOKEN_PERCENT,
TOKEN_HASH,
TOKEN_PLUS,
TOKEN_MINUS,
TOKEN_LTLT,
TOKEN_GTGT,
TOKEN_PIPE,
TOKEN_PIPEPIPE,
TOKEN_CARET,
TOKEN_AMP,
TOKEN_AMPAMP,
TOKEN_BANG,
TOKEN_TILDE,
TOKEN_QUESTION,
TOKEN_EQ,
TOKEN_LT,
TOKEN_GT,
TOKEN_LTEQ,
TOKEN_GTEQ,
TOKEN_EQEQ,
TOKEN_BANGEQ,
TOKEN_BREAK,
TOKEN_CONTINUE,
TOKEN_CLASS,
TOKEN_CONSTRUCT,
TOKEN_ELSE,
TOKEN_FALSE,
TOKEN_FOR,
TOKEN_FOREIGN,
TOKEN_IF,
TOKEN_IMPORT,
TOKEN_AS,
TOKEN_IN,
TOKEN_IS,
TOKEN_NULL,
TOKEN_RETURN,
TOKEN_STATIC,
TOKEN_SUPER,
TOKEN_THIS,
TOKEN_TRUE,
TOKEN_VAR,
TOKEN_WHILE,
TOKEN_FIELD,
TOKEN_STATIC_FIELD,
TOKEN_NAME,
TOKEN_NUMBER,
TOKEN_STRING,
TOKEN_INTERPOLATION,
TOKEN_LINE,
TOKEN_ERROR,
TOKEN_EOF
} TokenType;
typedef struct
{
TokenType type;
const char* start;
int length;
int line;
Value value;
} Token;
typedef struct
{
WrenVM* vm;
ObjModule* module;
const char* source;
const char* tokenStart;
const char* currentChar;
int currentLine;
Token next;
Token current;
Token previous;
int parens[MAX_INTERPOLATION_NESTING];
int numParens;
bool printErrors;
bool hasError;
} Parser;
typedef struct
{
const char* name;
int length;
int depth;
bool isUpvalue;
} Local;
typedef struct
{
bool isLocal;
int index;
} CompilerUpvalue;
typedef struct sLoop
{
int start;
int exitJump;
int body;
int scopeDepth;
struct sLoop* enclosing;
} Loop;
typedef enum
{
SIG_METHOD,
SIG_GETTER,
SIG_SETTER,
SIG_SUBSCRIPT,
SIG_SUBSCRIPT_SETTER,
SIG_INITIALIZER
} SignatureType;
typedef struct
{
const char* name;
int length;
SignatureType type;
int arity;
} Signature;
typedef struct
{
ObjString* name;
ObjMap* classAttributes;
ObjMap* methodAttributes;
SymbolTable fields;
IntBuffer methods;
IntBuffer staticMethods;
bool isForeign;
bool inStatic;
Signature* signature;
} ClassInfo;
struct sCompiler
{
Parser* parser;
struct sCompiler* parent;
Local locals[MAX_LOCALS];
int numLocals;
CompilerUpvalue upvalues[MAX_UPVALUES];
int scopeDepth;
int numSlots;
Loop* loop;
ClassInfo* enclosingClass;
ObjFn* fn;
ObjMap* constants;
bool isInitializer;
int numAttributes;
ObjMap* attributes;
};
typedef enum
{
SCOPE_LOCAL,
SCOPE_UPVALUE,
SCOPE_MODULE
} Scope;
typedef struct
{
int index;
Scope scope;
} Variable;
static void disallowAttributes(Compiler* compiler);
static void addToAttributeGroup(Compiler* compiler, Value group, Value key, Value value);
static void emitClassAttributes(Compiler* compiler, ClassInfo* classInfo);
static void copyAttributes(Compiler* compiler, ObjMap* into);
static void copyMethodAttributes(Compiler* compiler, bool isForeign,
bool isStatic, const char* fullSignature, int32_t length);
static const int stackEffects[] = {
#define OPCODE(_, effect) effect,
#include "wren_opcodes.h"
#undef OPCODE
};
static void printError(Parser* parser, int line, const char* label,
const char* format, va_list args)
{
parser->hasError = true;
if (!parser->printErrors) return;
if (parser->vm->config.errorFn == NULL) return;
char message[ERROR_MESSAGE_SIZE];
int length = sprintf(message, "%s: ", label);
length += vsprintf(message + length, format, args);
ASSERT(length < ERROR_MESSAGE_SIZE, "Error should not exceed buffer.");
ObjString* module = parser->module->name;
const char* module_name = module ? module->value : "<unknown>";
parser->vm->config.errorFn(parser->vm, WREN_ERROR_COMPILE,
module_name, line, message);
}
static void lexError(Parser* parser, const char* format, ...)
{
va_list args;
va_start(args, format);
printError(parser, parser->currentLine, "Error", format, args);
va_end(args);
}
static void error(Compiler* compiler, const char* format, ...)
{
Token* token = &compiler->parser->previous;
if (token->type == TOKEN_ERROR) return;
va_list args;
va_start(args, format);
if (token->type == TOKEN_LINE)
{
printError(compiler->parser, token->line, "Error at newline", format, args);
}
else if (token->type == TOKEN_EOF)
{
printError(compiler->parser, token->line,
"Error at end of file", format, args);
}
else
{
char label[10 + MAX_VARIABLE_NAME + 4 + 1];
if (token->length <= MAX_VARIABLE_NAME)
{
sprintf(label, "Error at '%.*s'", token->length, token->start);
}
else
{
sprintf(label, "Error at '%.*s...'", MAX_VARIABLE_NAME, token->start);
}
printError(compiler->parser, token->line, label, format, args);
}
va_end(args);
}
static int addConstant(Compiler* compiler, Value constant)
{
if (compiler->parser->hasError) return -1;
if (compiler->constants != NULL)
{
Value existing = wrenMapGet(compiler->constants, constant);
if (IS_NUM(existing)) return (int)AS_NUM(existing);
}
if (compiler->fn->constants.count < MAX_CONSTANTS)
{
if (IS_OBJ(constant)) wrenPushRoot(compiler->parser->vm, AS_OBJ(constant));
wrenValueBufferWrite(compiler->parser->vm, &compiler->fn->constants,
constant);
if (IS_OBJ(constant)) wrenPopRoot(compiler->parser->vm);
if (compiler->constants == NULL)
{
compiler->constants = wrenNewMap(compiler->parser->vm);
}
wrenMapSet(compiler->parser->vm, compiler->constants, constant,
NUM_VAL(compiler->fn->constants.count - 1));
}
else
{
error(compiler, "A function may only contain %d unique constants.",
MAX_CONSTANTS);
}
return compiler->fn->constants.count - 1;
}
static void initCompiler(Compiler* compiler, Parser* parser, Compiler* parent,
bool isMethod)
{
compiler->parser = parser;
compiler->parent = parent;
compiler->loop = NULL;
compiler->enclosingClass = NULL;
compiler->isInitializer = false;
compiler->fn = NULL;
compiler->constants = NULL;
parser->vm->compiler = compiler;
compiler->numLocals = 1;
compiler->numSlots = compiler->numLocals;
if (isMethod)
{
compiler->locals[0].name = "this";
compiler->locals[0].length = 4;
}
else
{
compiler->locals[0].name = NULL;
compiler->locals[0].length = 0;
}
compiler->locals[0].depth = -1;
compiler->locals[0].isUpvalue = false;
if (parent == NULL)
{
compiler->scopeDepth = -1;
}
else
{
compiler->scopeDepth = 0;
}
compiler->numAttributes = 0;
compiler->attributes = wrenNewMap(parser->vm);
compiler->fn = wrenNewFunction(parser->vm, parser->module,
compiler->numLocals);
}
typedef struct
{
const char* identifier;
size_t length;
TokenType tokenType;
} Keyword;
static Keyword keywords[] =
{
{"break", 5, TOKEN_BREAK},
{"continue", 8, TOKEN_CONTINUE},
{"class", 5, TOKEN_CLASS},
{"construct", 9, TOKEN_CONSTRUCT},
{"else", 4, TOKEN_ELSE},
{"false", 5, TOKEN_FALSE},
{"for", 3, TOKEN_FOR},
{"foreign", 7, TOKEN_FOREIGN},
{"if", 2, TOKEN_IF},
{"import", 6, TOKEN_IMPORT},
{"as", 2, TOKEN_AS},
{"in", 2, TOKEN_IN},
{"is", 2, TOKEN_IS},
{"null", 4, TOKEN_NULL},
{"return", 6, TOKEN_RETURN},
{"static", 6, TOKEN_STATIC},
{"super", 5, TOKEN_SUPER},
{"this", 4, TOKEN_THIS},
{"true", 4, TOKEN_TRUE},
{"var", 3, TOKEN_VAR},
{"while", 5, TOKEN_WHILE},
{NULL, 0, TOKEN_EOF} };
static bool isName(char c)
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
}
static bool isDigit(char c)
{
return c >= '0' && c <= '9';
}
static char peekChar(Parser* parser)
{
return *parser->currentChar;
}
static char peekNextChar(Parser* parser)
{
if (peekChar(parser) == '\0') return '\0';
return *(parser->currentChar + 1);
}
static char nextChar(Parser* parser)
{
char c = peekChar(parser);
parser->currentChar++;
if (c == '\n') parser->currentLine++;
return c;
}
static bool matchChar(Parser* parser, char c)
{
if (peekChar(parser) != c) return false;
nextChar(parser);
return true;
}
static void makeToken(Parser* parser, TokenType type)
{
parser->next.type = type;
parser->next.start = parser->tokenStart;
parser->next.length = (int)(parser->currentChar - parser->tokenStart);
parser->next.line = parser->currentLine;
if (type == TOKEN_LINE) parser->next.line--;
}
static void twoCharToken(Parser* parser, char c, TokenType two, TokenType one)
{
makeToken(parser, matchChar(parser, c) ? two : one);
}
static void skipLineComment(Parser* parser)
{
while (peekChar(parser) != '\n' && peekChar(parser) != '\0')
{
nextChar(parser);
}
}
static void skipBlockComment(Parser* parser)
{
int nesting = 1;
while (nesting > 0)
{
if (peekChar(parser) == '\0')
{
lexError(parser, "Unterminated block comment.");
return;
}
if (peekChar(parser) == '/' && peekNextChar(parser) == '*')
{
nextChar(parser);
nextChar(parser);
nesting++;
continue;
}
if (peekChar(parser) == '*' && peekNextChar(parser) == '/')
{
nextChar(parser);
nextChar(parser);
nesting--;
continue;
}
nextChar(parser);
}
}
static int readHexDigit(Parser* parser)
{
char c = nextChar(parser);
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
parser->currentChar--;
return -1;
}
static void makeNumber(Parser* parser, bool isHex)
{
errno = 0;
if (isHex)
{
parser->next.value = NUM_VAL((double)strtoll(parser->tokenStart, NULL, 16));
}
else
{
parser->next.value = NUM_VAL(strtod(parser->tokenStart, NULL));
}
if (errno == ERANGE)
{
lexError(parser, "Number literal was too large (%d).", sizeof(long int));
parser->next.value = NUM_VAL(0);
}
makeToken(parser, TOKEN_NUMBER);
}
static void readHexNumber(Parser* parser)
{
nextChar(parser);
while (readHexDigit(parser) != -1) continue;
makeNumber(parser, true);
}
static void readNumber(Parser* parser)
{
while (isDigit(peekChar(parser))) nextChar(parser);
if (peekChar(parser) == '.' && isDigit(peekNextChar(parser)))
{
nextChar(parser);
while (isDigit(peekChar(parser))) nextChar(parser);
}
if (matchChar(parser, 'e') || matchChar(parser, 'E'))
{
if(!matchChar(parser, '+'))
{
matchChar(parser, '-');
}
if (!isDigit(peekChar(parser)))
{
lexError(parser, "Unterminated scientific notation.");
}
while (isDigit(peekChar(parser))) nextChar(parser);
}
makeNumber(parser, false);
}
static void readName(Parser* parser, TokenType type, char firstChar)
{
ByteBuffer string;
wrenByteBufferInit(&string);
wrenByteBufferWrite(parser->vm, &string, firstChar);
while (isName(peekChar(parser)) || isDigit(peekChar(parser)))
{
char c = nextChar(parser);
wrenByteBufferWrite(parser->vm, &string, c);
}
size_t length = parser->currentChar - parser->tokenStart;
for (int i = 0; keywords[i].identifier != NULL; i++)
{
if (length == keywords[i].length &&
memcmp(parser->tokenStart, keywords[i].identifier, length) == 0)
{
type = keywords[i].tokenType;
break;
}
}
parser->next.value = wrenNewStringLength(parser->vm,
(char*)string.data, string.count);
wrenByteBufferClear(parser->vm, &string);
makeToken(parser, type);
}
static int readHexEscape(Parser* parser, int digits, const char* description)
{
int value = 0;
for (int i = 0; i < digits; i++)
{
if (peekChar(parser) == '"' || peekChar(parser) == '\0')
{
lexError(parser, "Incomplete %s escape sequence.", description);
parser->currentChar--;
break;
}
int digit = readHexDigit(parser);
if (digit == -1)
{
lexError(parser, "Invalid %s escape sequence.", description);
break;
}
value = (value * 16) | digit;
}
return value;
}
static void readUnicodeEscape(Parser* parser, ByteBuffer* string, int length)
{
int value = readHexEscape(parser, length, "Unicode");
int numBytes = wrenUtf8EncodeNumBytes(value);
if (numBytes != 0)
{
wrenByteBufferFill(parser->vm, string, 0, numBytes);
wrenUtf8Encode(value, string->data + string->count - numBytes);
}
}
static void readRawString(Parser* parser)
{
ByteBuffer string;
wrenByteBufferInit(&string);
TokenType type = TOKEN_STRING;
nextChar(parser);
nextChar(parser);
int skipStart = 0;
int firstNewline = -1;
int skipEnd = -1;
int lastNewline = -1;
for (;;)
{
char c = nextChar(parser);
char c1 = peekChar(parser);
char c2 = peekNextChar(parser);
if(c == '\n') {
lastNewline = string.count;
skipEnd = lastNewline;
firstNewline = firstNewline == -1 ? string.count : firstNewline;
}
if(c == '"' && c1 == '"' && c2 == '"') break;
bool isWhitespace = c == ' ' || c == '\t';
skipEnd = c == '\n' || isWhitespace ? skipEnd : -1;
bool skippable = skipStart != -1 && isWhitespace && firstNewline == -1;
skipStart = skippable ? string.count + 1 : skipStart;
if (firstNewline == -1 && !isWhitespace && c != '\n') skipStart = -1;
if (c == '\0' || c1 == '\0' || c2 == '\0')
{
lexError(parser, "Unterminated raw string.");
parser->currentChar--;
break;
}
wrenByteBufferWrite(parser->vm, &string, c);
}
nextChar(parser);
nextChar(parser);
int offset = 0;
int count = string.count;
if(firstNewline != -1 && skipStart == firstNewline) offset = firstNewline + 1;
if(lastNewline != -1 && skipEnd == lastNewline) count = lastNewline;
count -= (offset > count) ? count : offset;
parser->next.value = wrenNewStringLength(parser->vm,
((char*)string.data) + offset, count);
wrenByteBufferClear(parser->vm, &string);
makeToken(parser, type);
}
static void readString(Parser* parser)
{
ByteBuffer string;
TokenType type = TOKEN_STRING;
wrenByteBufferInit(&string);
for (;;)
{
char c = nextChar(parser);
if (c == '"') break;
if (c == '\0')
{
lexError(parser, "Unterminated string.");
parser->currentChar--;
break;
}
if (c == '%')
{
if (parser->numParens < MAX_INTERPOLATION_NESTING)
{
if (nextChar(parser) != '(') lexError(parser, "Expect '(' after '%%'.");
parser->parens[parser->numParens++] = 1;
type = TOKEN_INTERPOLATION;
break;
}
lexError(parser, "Interpolation may only nest %d levels deep.",
MAX_INTERPOLATION_NESTING);
}
if (c == '\\')
{
switch (nextChar(parser))
{
case '"': wrenByteBufferWrite(parser->vm, &string, '"'); 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 'e': wrenByteBufferWrite(parser->vm, &string, '\33'); 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, 4); break;
case 'U': readUnicodeEscape(parser, &string, 8); break;
case 'v': wrenByteBufferWrite(parser->vm, &string, '\v'); break;
case 'x':
wrenByteBufferWrite(parser->vm, &string,
(uint8_t)readHexEscape(parser, 2, "byte"));
break;
default:
lexError(parser, "Invalid escape character '%c'.",
*(parser->currentChar - 1));
break;
}
}
else
{
wrenByteBufferWrite(parser->vm, &string, c);
}
}
parser->next.value = wrenNewStringLength(parser->vm,
(char*)string.data, string.count);
wrenByteBufferClear(parser->vm, &string);
makeToken(parser, type);
}
static void nextToken(Parser* parser)
{
parser->previous = parser->current;
parser->current = parser->next;
if (parser->next.type == TOKEN_EOF) return;
if (parser->current.type == TOKEN_EOF) return;
while (peekChar(parser) != '\0')
{
parser->tokenStart = parser->currentChar;
char c = nextChar(parser);
switch (c)
{
case '(':
if (parser->numParens > 0) parser->parens[parser->numParens - 1]++;
makeToken(parser, TOKEN_LEFT_PAREN);
return;
case ')':
if (parser->numParens > 0 &&
--parser->parens[parser->numParens - 1] == 0)
{
parser->numParens--;
readString(parser);
return;
}
makeToken(parser, TOKEN_RIGHT_PAREN);
return;
case '[': makeToken(parser, TOKEN_LEFT_BRACKET); return;
case ']': makeToken(parser, TOKEN_RIGHT_BRACKET); return;
case '{': makeToken(parser, TOKEN_LEFT_BRACE); return;
case '}': makeToken(parser, TOKEN_RIGHT_BRACE); return;
case ':': makeToken(parser, TOKEN_COLON); return;
case ',': makeToken(parser, TOKEN_COMMA); return;
case '*': makeToken(parser, TOKEN_STAR); return;
case '%': makeToken(parser, TOKEN_PERCENT); return;
case '#': {
if (parser->currentLine == 1 && peekChar(parser) == '!' && peekNextChar(parser) == '/')
{
skipLineComment(parser);
break;
}
makeToken(parser, TOKEN_HASH);
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 (matchChar(parser, '/'))
{
skipLineComment(parser);
break;
}
if (matchChar(parser, '*'))
{
skipBlockComment(parser);
break;
}
makeToken(parser, TOKEN_SLASH);
return;
case '<':
if (matchChar(parser, '<'))
{
makeToken(parser, TOKEN_LTLT);
}
else
{
twoCharToken(parser, '=', TOKEN_LTEQ, TOKEN_LT);
}
return;
case '>':
if (matchChar(parser, '>'))
{
makeToken(parser, TOKEN_GTGT);
}
else
{
twoCharToken(parser, '=', TOKEN_GTEQ, TOKEN_GT);
}
return;
case '\n':
makeToken(parser, TOKEN_LINE);
return;
case ' ':
case '\r':
case '\t':
while (peekChar(parser) == ' ' ||
peekChar(parser) == '\r' ||
peekChar(parser) == '\t')
{
nextChar(parser);
}
break;
case '"': {
if(peekChar(parser) == '"' && peekNextChar(parser) == '"') {
readRawString(parser);
return;
}
readString(parser); return;
}
case '_':
readName(parser,
peekChar(parser) == '_' ? TOKEN_STATIC_FIELD : TOKEN_FIELD, c);
return;
case '0':
if (peekChar(parser) == 'x')
{
readHexNumber(parser);
return;
}
readNumber(parser);
return;
default:
if (isName(c))
{
readName(parser, TOKEN_NAME, c);
}
else if (isDigit(c))
{
readNumber(parser);
}
else
{
if (c >= 32 && c <= 126)
{
lexError(parser, "Invalid character '%c'.", c);
}
else
{
lexError(parser, "Invalid byte 0x%x.", (uint8_t)c);
}
parser->next.type = TOKEN_ERROR;
parser->next.length = 0;
}
return;
}
}
parser->tokenStart = parser->currentChar;
makeToken(parser, TOKEN_EOF);
}
static TokenType peek(Compiler* compiler)
{
return compiler->parser->current.type;
}
static TokenType peekNext(Compiler* compiler)
{
return compiler->parser->next.type;
}
static bool match(Compiler* compiler, TokenType expected)
{
if (peek(compiler) != expected) return false;
nextToken(compiler->parser);
return true;
}
static void consume(Compiler* compiler, TokenType expected,
const char* errorMessage)
{
nextToken(compiler->parser);
if (compiler->parser->previous.type != expected)
{
error(compiler, errorMessage);
if (compiler->parser->current.type == expected) nextToken(compiler->parser);
}
}
static bool matchLine(Compiler* compiler)
{
if (!match(compiler, TOKEN_LINE)) return false;
while (match(compiler, TOKEN_LINE));
return true;
}
static void ignoreNewlines(Compiler* compiler)
{
matchLine(compiler);
}
static void consumeLine(Compiler* compiler, const char* errorMessage)
{
consume(compiler, TOKEN_LINE, errorMessage);
ignoreNewlines(compiler);
}
static void allowLineBeforeDot(Compiler* compiler) {
if (peek(compiler) == TOKEN_LINE && peekNext(compiler) == TOKEN_DOT) {
nextToken(compiler->parser);
}
}
static int emitByte(Compiler* compiler, int byte)
{
wrenByteBufferWrite(compiler->parser->vm, &compiler->fn->code, (uint8_t)byte);
wrenIntBufferWrite(compiler->parser->vm, &compiler->fn->debug->sourceLines,
compiler->parser->previous.line);
return compiler->fn->code.count - 1;
}
static void emitOp(Compiler* compiler, Code instruction)
{
emitByte(compiler, instruction);
compiler->numSlots += stackEffects[instruction];
if (compiler->numSlots > compiler->fn->maxSlots)
{
compiler->fn->maxSlots = compiler->numSlots;
}
}
static void emitShort(Compiler* compiler, int arg)
{
emitByte(compiler, (arg >> 8) & 0xff);
emitByte(compiler, arg & 0xff);
}
static int emitByteArg(Compiler* compiler, Code instruction, int arg)
{
emitOp(compiler, instruction);
return emitByte(compiler, arg);
}
static void emitShortArg(Compiler* compiler, Code instruction, int arg)
{
emitOp(compiler, instruction);
emitShort(compiler, arg);
}
static int emitJump(Compiler* compiler, Code instruction)
{
emitOp(compiler, instruction);
emitByte(compiler, 0xff);
return emitByte(compiler, 0xff) - 1;
}
static void emitConstant(Compiler* compiler, Value value)
{
int constant = addConstant(compiler, value);
emitShortArg(compiler, CODE_CONSTANT, constant);
}
static int addLocal(Compiler* compiler, const char* name, int length)
{
Local* local = &compiler->locals[compiler->numLocals];
local->name = name;
local->length = length;
local->depth = compiler->scopeDepth;
local->isUpvalue = false;
return compiler->numLocals++;
}
static int declareVariable(Compiler* compiler, Token* token)
{
if (token == NULL) token = &compiler->parser->previous;
if (token->length > MAX_VARIABLE_NAME)
{
error(compiler, "Variable name cannot be longer than %d characters.",
MAX_VARIABLE_NAME);
}
if (compiler->scopeDepth == -1)
{
int line = -1;
int symbol = wrenDefineVariable(compiler->parser->vm,
compiler->parser->module,
token->start, token->length,
NULL_VAL, &line);
if (symbol == -1)
{
error(compiler, "Module variable is already defined.");
}
else if (symbol == -2)
{
error(compiler, "Too many module variables defined.");
}
else if (symbol == -3)
{
error(compiler,
"Variable '%.*s' referenced before this definition (first use at line %d).",
token->length, token->start, line);
}
return symbol;
}
for (int i = compiler->numLocals - 1; i >= 0; i--)
{
Local* local = &compiler->locals[i];
if (local->depth < compiler->scopeDepth) break;
if (local->length == token->length &&
memcmp(local->name, token->start, token->length) == 0)
{
error(compiler, "Variable is already declared in this scope.");
return i;
}
}
if (compiler->numLocals == MAX_LOCALS)
{
error(compiler, "Cannot declare more than %d variables in one scope.",
MAX_LOCALS);
return -1;
}
return addLocal(compiler, token->start, token->length);
}
static int declareNamedVariable(Compiler* compiler)
{
consume(compiler, TOKEN_NAME, "Expect variable name.");
return declareVariable(compiler, NULL);
}
static void defineVariable(Compiler* compiler, int symbol)
{
if (compiler->scopeDepth >= 0) return;
emitShortArg(compiler, CODE_STORE_MODULE_VAR, symbol);
emitOp(compiler, CODE_POP);
}
static void pushScope(Compiler* compiler)
{
compiler->scopeDepth++;
}
static int discardLocals(Compiler* compiler, int depth)
{
ASSERT(compiler->scopeDepth > -1, "Cannot exit top-level scope.");
int local = compiler->numLocals - 1;
while (local >= 0 && compiler->locals[local].depth >= depth)
{
if (compiler->locals[local].isUpvalue)
{
emitByte(compiler, CODE_CLOSE_UPVALUE);
}
else
{
emitByte(compiler, CODE_POP);
}
local--;
}
return compiler->numLocals - local - 1;
}
static void popScope(Compiler* compiler)
{
int popped = discardLocals(compiler, compiler->scopeDepth);
compiler->numLocals -= popped;
compiler->numSlots -= popped;
compiler->scopeDepth--;
}
static int resolveLocal(Compiler* compiler, const char* name, int length)
{
for (int i = compiler->numLocals - 1; i >= 0; i--)
{
if (compiler->locals[i].length == length &&
memcmp(name, compiler->locals[i].name, length) == 0)
{
return i;
}
}
return -1;
}
static int addUpvalue(Compiler* compiler, bool isLocal, int index)
{
for (int i = 0; i < compiler->fn->numUpvalues; i++)
{
CompilerUpvalue* upvalue = &compiler->upvalues[i];
if (upvalue->index == index && upvalue->isLocal == isLocal) return i;
}
compiler->upvalues[compiler->fn->numUpvalues].isLocal = isLocal;
compiler->upvalues[compiler->fn->numUpvalues].index = index;
return compiler->fn->numUpvalues++;
}
static int findUpvalue(Compiler* compiler, const char* name, int length)
{
if (compiler->parent == NULL) return -1;
if (name[0] != '_' && compiler->parent->enclosingClass != NULL) return -1;
int local = resolveLocal(compiler->parent, name, length);
if (local != -1)
{
compiler->parent->locals[local].isUpvalue = true;
return addUpvalue(compiler, true, local);
}
int upvalue = findUpvalue(compiler->parent, name, length);
if (upvalue != -1)
{
return addUpvalue(compiler, false, upvalue);
}
return -1;
}
static Variable resolveNonmodule(Compiler* compiler,
const char* name, int length)
{
Variable variable;
variable.scope = SCOPE_LOCAL;
variable.index = resolveLocal(compiler, name, length);
if (variable.index != -1) return variable;
variable.scope = SCOPE_UPVALUE;
variable.index = findUpvalue(compiler, name, length);
return variable;
}
static Variable resolveName(Compiler* compiler, const char* name, int length)
{
Variable variable = resolveNonmodule(compiler, name, length);
if (variable.index != -1) return variable;
variable.scope = SCOPE_MODULE;
variable.index = wrenSymbolTableFind(&compiler->parser->module->variableNames,
name, length);
return variable;
}
static void loadLocal(Compiler* compiler, int slot)
{
if (slot <= 8)
{
emitOp(compiler, (Code)(CODE_LOAD_LOCAL_0 + slot));
return;
}
emitByteArg(compiler, CODE_LOAD_LOCAL, slot);
}
static ObjFn* endCompiler(Compiler* compiler,
const char* debugName, int debugNameLength)
{
if (compiler->parser->hasError)
{
compiler->parser->vm->compiler = compiler->parent;
return NULL;
}
emitOp(compiler, CODE_END);
wrenFunctionBindName(compiler->parser->vm, compiler->fn,
debugName, debugNameLength);
if (compiler->parent != NULL)
{
int constant = addConstant(compiler->parent, OBJ_VAL(compiler->fn));
emitShortArg(compiler->parent, CODE_CLOSURE, constant);
for (int i = 0; i < compiler->fn->numUpvalues; i++)
{
emitByte(compiler->parent, compiler->upvalues[i].isLocal ? 1 : 0);
emitByte(compiler->parent, compiler->upvalues[i].index);
}
}
compiler->parser->vm->compiler = compiler->parent;
#if WREN_DEBUG_DUMP_COMPILED_CODE
wrenDumpCode(compiler->parser->vm, compiler->fn);
#endif
return compiler->fn;
}
typedef enum
{
PREC_NONE,
PREC_LOWEST,
PREC_ASSIGNMENT, PREC_CONDITIONAL, PREC_LOGICAL_OR, PREC_LOGICAL_AND, PREC_EQUALITY, PREC_IS, PREC_COMPARISON, PREC_BITWISE_OR, PREC_BITWISE_XOR, PREC_BITWISE_AND, PREC_BITWISE_SHIFT, PREC_RANGE, PREC_TERM, PREC_FACTOR, PREC_UNARY, PREC_CALL, PREC_PRIMARY
} Precedence;
typedef void (*GrammarFn)(Compiler*, bool canAssign);
typedef void (*SignatureFn)(Compiler* compiler, Signature* signature);
typedef struct
{
GrammarFn prefix;
GrammarFn infix;
SignatureFn method;
Precedence precedence;
const char* name;
} GrammarRule;
static GrammarRule* getRule(TokenType type);
static void expression(Compiler* compiler);
static void statement(Compiler* compiler);
static void definition(Compiler* compiler);
static void parsePrecedence(Compiler* compiler, Precedence precedence);
static void patchJump(Compiler* compiler, int offset)
{
int jump = compiler->fn->code.count - offset - 2;
if (jump > MAX_JUMP) error(compiler, "Too much code to jump over.");
compiler->fn->code.data[offset] = (jump >> 8) & 0xff;
compiler->fn->code.data[offset + 1] = jump & 0xff;
}
static bool finishBlock(Compiler* compiler)
{
if (match(compiler, TOKEN_RIGHT_BRACE)) return false;
if (!matchLine(compiler))
{
expression(compiler);
consume(compiler, TOKEN_RIGHT_BRACE, "Expect '}' at end of block.");
return true;
}
if (match(compiler, TOKEN_RIGHT_BRACE)) return false;
do
{
definition(compiler);
consumeLine(compiler, "Expect newline after statement.");
}
while (peek(compiler) != TOKEN_RIGHT_BRACE && peek(compiler) != TOKEN_EOF);
consume(compiler, TOKEN_RIGHT_BRACE, "Expect '}' at end of block.");
return false;
}
static void finishBody(Compiler* compiler)
{
bool isExpressionBody = finishBlock(compiler);
if (compiler->isInitializer)
{
if (isExpressionBody) emitOp(compiler, CODE_POP);
emitOp(compiler, CODE_LOAD_LOCAL_0);
}
else if (!isExpressionBody)
{
emitOp(compiler, CODE_NULL);
}
emitOp(compiler, CODE_RETURN);
}
static void validateNumParameters(Compiler* compiler, int numArgs)
{
if (numArgs == MAX_PARAMETERS + 1)
{
error(compiler, "Methods cannot have more than %d parameters.",
MAX_PARAMETERS);
}
}
static void finishParameterList(Compiler* compiler, Signature* signature)
{
do
{
ignoreNewlines(compiler);
validateNumParameters(compiler, ++signature->arity);
declareNamedVariable(compiler);
}
while (match(compiler, TOKEN_COMMA));
}
static int methodSymbol(Compiler* compiler, const char* name, int length)
{
return wrenSymbolTableEnsure(compiler->parser->vm,
&compiler->parser->vm->methodNames, name, length);
}
static void signatureParameterList(char name[MAX_METHOD_SIGNATURE], int* length,
int numParams, char leftBracket, char rightBracket)
{
name[(*length)++] = leftBracket;
for (int i = 0; i < numParams && i < MAX_PARAMETERS; i++)
{
if (i > 0) name[(*length)++] = ',';
name[(*length)++] = '_';
}
name[(*length)++] = rightBracket;
}
static void signatureToString(Signature* signature,
char name[MAX_METHOD_SIGNATURE], int* length)
{
*length = 0;
memcpy(name + *length, signature->name, signature->length);
*length += signature->length;
switch (signature->type)
{
case SIG_METHOD:
signatureParameterList(name, length, signature->arity, '(', ')');
break;
case SIG_GETTER:
break;
case SIG_SETTER:
name[(*length)++] = '=';
signatureParameterList(name, length, 1, '(', ')');
break;
case SIG_SUBSCRIPT:
signatureParameterList(name, length, signature->arity, '[', ']');
break;
case SIG_SUBSCRIPT_SETTER:
signatureParameterList(name, length, signature->arity - 1, '[', ']');
name[(*length)++] = '=';
signatureParameterList(name, length, 1, '(', ')');
break;
case SIG_INITIALIZER:
memcpy(name, "init ", 5);
memcpy(name + 5, signature->name, signature->length);
*length = 5 + signature->length;
signatureParameterList(name, length, signature->arity, '(', ')');
break;
}
name[*length] = '\0';
}
static int signatureSymbol(Compiler* compiler, Signature* signature)
{
char name[MAX_METHOD_SIGNATURE];
int length;
signatureToString(signature, name, &length);
return methodSymbol(compiler, name, length);
}
static Signature signatureFromToken(Compiler* compiler, SignatureType type)
{
Signature signature;
Token* token = &compiler->parser->previous;
signature.name = token->start;
signature.length = token->length;
signature.type = type;
signature.arity = 0;
if (signature.length > MAX_METHOD_NAME)
{
error(compiler, "Method names cannot be longer than %d characters.",
MAX_METHOD_NAME);
signature.length = MAX_METHOD_NAME;
}
return signature;
}
static void finishArgumentList(Compiler* compiler, Signature* signature)
{
do
{
ignoreNewlines(compiler);
validateNumParameters(compiler, ++signature->arity);
expression(compiler);
}
while (match(compiler, TOKEN_COMMA));
ignoreNewlines(compiler);
}
static void callSignature(Compiler* compiler, Code instruction,
Signature* signature)
{
int symbol = signatureSymbol(compiler, signature);
emitShortArg(compiler, (Code)(instruction + signature->arity), symbol);
if (instruction == CODE_SUPER_0)
{
emitShort(compiler, addConstant(compiler, NULL_VAL));
}
}
static void callMethod(Compiler* compiler, int numArgs, const char* name,
int length)
{
int symbol = methodSymbol(compiler, name, length);
emitShortArg(compiler, (Code)(CODE_CALL_0 + numArgs), symbol);
}
static void methodCall(Compiler* compiler, Code instruction,
Signature* signature)
{
Signature called = { signature->name, signature->length, SIG_GETTER, 0 };
if (match(compiler, TOKEN_LEFT_PAREN))
{
called.type = SIG_METHOD;
ignoreNewlines(compiler);
if (peek(compiler) != TOKEN_RIGHT_PAREN)
{
finishArgumentList(compiler, &called);
}
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after arguments.");
}
if (match(compiler, TOKEN_LEFT_BRACE))
{
called.type = SIG_METHOD;
called.arity++;
Compiler fnCompiler;
initCompiler(&fnCompiler, compiler->parser, compiler, false);
Signature fnSignature = { "", 0, SIG_METHOD, 0 };
if (match(compiler, TOKEN_PIPE))
{
finishParameterList(&fnCompiler, &fnSignature);
consume(compiler, TOKEN_PIPE, "Expect '|' after function parameters.");
}
fnCompiler.fn->arity = fnSignature.arity;
finishBody(&fnCompiler);
char blockName[MAX_METHOD_SIGNATURE + 15];
int blockLength;
signatureToString(&called, blockName, &blockLength);
memmove(blockName + blockLength, " block argument", 16);
endCompiler(&fnCompiler, blockName, blockLength + 15);
}
if (signature->type == SIG_INITIALIZER)
{
if (called.type != SIG_METHOD)
{
error(compiler, "A superclass constructor must have an argument list.");
}
called.type = SIG_INITIALIZER;
}
callSignature(compiler, instruction, &called);
}
static void namedCall(Compiler* compiler, bool canAssign, Code instruction)
{
Signature signature = signatureFromToken(compiler, SIG_GETTER);
if (canAssign && match(compiler, TOKEN_EQ))
{
ignoreNewlines(compiler);
signature.type = SIG_SETTER;
signature.arity = 1;
expression(compiler);
callSignature(compiler, instruction, &signature);
}
else
{
methodCall(compiler, instruction, &signature);
allowLineBeforeDot(compiler);
}
}
static void loadVariable(Compiler* compiler, Variable variable)
{
switch (variable.scope)
{
case SCOPE_LOCAL:
loadLocal(compiler, variable.index);
break;
case SCOPE_UPVALUE:
emitByteArg(compiler, CODE_LOAD_UPVALUE, variable.index);
break;
case SCOPE_MODULE:
emitShortArg(compiler, CODE_LOAD_MODULE_VAR, variable.index);
break;
default:
UNREACHABLE();
}
}
static void loadThis(Compiler* compiler)
{
loadVariable(compiler, resolveNonmodule(compiler, "this", 4));
}
static void loadCoreVariable(Compiler* compiler, const char* name)
{
int symbol = wrenSymbolTableFind(&compiler->parser->module->variableNames,
name, strlen(name));
ASSERT(symbol != -1, "Should have already defined core name.");
emitShortArg(compiler, CODE_LOAD_MODULE_VAR, symbol);
}
static void grouping(Compiler* compiler, bool canAssign)
{
expression(compiler);
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after expression.");
}
static void list(Compiler* compiler, bool canAssign)
{
loadCoreVariable(compiler, "List");
callMethod(compiler, 0, "new()", 5);
do
{
ignoreNewlines(compiler);
if (peek(compiler) == TOKEN_RIGHT_BRACKET) break;
expression(compiler);
callMethod(compiler, 1, "addCore_(_)", 11);
} while (match(compiler, TOKEN_COMMA));
ignoreNewlines(compiler);
consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after list elements.");
}
static void map(Compiler* compiler, bool canAssign)
{
loadCoreVariable(compiler, "Map");
callMethod(compiler, 0, "new()", 5);
do
{
ignoreNewlines(compiler);
if (peek(compiler) == TOKEN_RIGHT_BRACE) break;
parsePrecedence(compiler, PREC_UNARY);
consume(compiler, TOKEN_COLON, "Expect ':' after map key.");
ignoreNewlines(compiler);
expression(compiler);
callMethod(compiler, 2, "addCore_(_,_)", 13);
} while (match(compiler, TOKEN_COMMA));
ignoreNewlines(compiler);
consume(compiler, TOKEN_RIGHT_BRACE, "Expect '}' after map entries.");
}
static void unaryOp(Compiler* compiler, bool canAssign)
{
GrammarRule* rule = getRule(compiler->parser->previous.type);
ignoreNewlines(compiler);
parsePrecedence(compiler, (Precedence)(PREC_UNARY + 1));
callMethod(compiler, 0, rule->name, 1);
}
static void boolean(Compiler* compiler, bool canAssign)
{
emitOp(compiler,
compiler->parser->previous.type == TOKEN_FALSE ? CODE_FALSE : CODE_TRUE);
}
static Compiler* getEnclosingClassCompiler(Compiler* compiler)
{
while (compiler != NULL)
{
if (compiler->enclosingClass != NULL) return compiler;
compiler = compiler->parent;
}
return NULL;
}
static ClassInfo* getEnclosingClass(Compiler* compiler)
{
compiler = getEnclosingClassCompiler(compiler);
return compiler == NULL ? NULL : compiler->enclosingClass;
}
static void field(Compiler* compiler, bool canAssign)
{
int field = MAX_FIELDS;
ClassInfo* enclosingClass = getEnclosingClass(compiler);
if (enclosingClass == NULL)
{
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.");
}
else
{
field = wrenSymbolTableEnsure(compiler->parser->vm, &enclosingClass->fields,
compiler->parser->previous.start,
compiler->parser->previous.length);
if (field >= MAX_FIELDS)
{
error(compiler, "A class can only have %d fields.", MAX_FIELDS);
}
}
bool isLoad = true;
if (canAssign && match(compiler, TOKEN_EQ))
{
expression(compiler);
isLoad = false;
}
if (compiler->parent != NULL &&
compiler->parent->enclosingClass == enclosingClass)
{
emitByteArg(compiler, isLoad ? CODE_LOAD_FIELD_THIS : CODE_STORE_FIELD_THIS,
field);
}
else
{
loadThis(compiler);
emitByteArg(compiler, isLoad ? CODE_LOAD_FIELD : CODE_STORE_FIELD, field);
}
allowLineBeforeDot(compiler);
}
static void bareName(Compiler* compiler, bool canAssign, Variable variable)
{
if (canAssign && match(compiler, TOKEN_EQ))
{
expression(compiler);
switch (variable.scope)
{
case SCOPE_LOCAL:
emitByteArg(compiler, CODE_STORE_LOCAL, variable.index);
break;
case SCOPE_UPVALUE:
emitByteArg(compiler, CODE_STORE_UPVALUE, variable.index);
break;
case SCOPE_MODULE:
emitShortArg(compiler, CODE_STORE_MODULE_VAR, variable.index);
break;
default:
UNREACHABLE();
}
return;
}
loadVariable(compiler, variable);
allowLineBeforeDot(compiler);
}
static void staticField(Compiler* compiler, bool canAssign)
{
Compiler* classCompiler = getEnclosingClassCompiler(compiler);
if (classCompiler == NULL)
{
error(compiler, "Cannot use a static field outside of a class definition.");
return;
}
Token* token = &compiler->parser->previous;
if (resolveLocal(classCompiler, token->start, token->length) == -1)
{
int symbol = declareVariable(classCompiler, NULL);
emitOp(classCompiler, CODE_NULL);
defineVariable(classCompiler, symbol);
}
Variable variable = resolveName(compiler, token->start, token->length);
bareName(compiler, canAssign, variable);
}
static void name(Compiler* compiler, bool canAssign)
{
Token* token = &compiler->parser->previous;
Variable variable = resolveNonmodule(compiler, token->start, token->length);
if (variable.index != -1)
{
bareName(compiler, canAssign, variable);
return;
}
if (wrenIsLocalName(token->start) && getEnclosingClass(compiler) != NULL)
{
loadThis(compiler);
namedCall(compiler, canAssign, CODE_CALL_0);
return;
}
variable.scope = SCOPE_MODULE;
variable.index = wrenSymbolTableFind(&compiler->parser->module->variableNames,
token->start, token->length);
if (variable.index == -1)
{
variable.index = wrenDeclareVariable(compiler->parser->vm,
compiler->parser->module,
token->start, token->length,
token->line);
if (variable.index == -2)
{
error(compiler, "Too many module variables defined.");
}
}
bareName(compiler, canAssign, variable);
}
static void null(Compiler* compiler, bool canAssign)
{
emitOp(compiler, CODE_NULL);
}
static void literal(Compiler* compiler, bool canAssign)
{
emitConstant(compiler, compiler->parser->previous.value);
}
static void stringInterpolation(Compiler* compiler, bool canAssign)
{
loadCoreVariable(compiler, "List");
callMethod(compiler, 0, "new()", 5);
do
{
literal(compiler, false);
callMethod(compiler, 1, "addCore_(_)", 11);
ignoreNewlines(compiler);
expression(compiler);
callMethod(compiler, 1, "addCore_(_)", 11);
ignoreNewlines(compiler);
} while (match(compiler, TOKEN_INTERPOLATION));
consume(compiler, TOKEN_STRING, "Expect end of string interpolation.");
literal(compiler, false);
callMethod(compiler, 1, "addCore_(_)", 11);
callMethod(compiler, 0, "join()", 6);
}
static void super_(Compiler* compiler, bool canAssign)
{
ClassInfo* enclosingClass = getEnclosingClass(compiler);
if (enclosingClass == NULL)
{
error(compiler, "Cannot use 'super' outside of a method.");
}
loadThis(compiler);
if (match(compiler, TOKEN_DOT))
{
consume(compiler, TOKEN_NAME, "Expect method name after 'super.'.");
namedCall(compiler, canAssign, CODE_SUPER_0);
}
else if (enclosingClass != NULL)
{
methodCall(compiler, CODE_SUPER_0, enclosingClass->signature);
}
}
static void this_(Compiler* compiler, bool canAssign)
{
if (getEnclosingClass(compiler) == NULL)
{
error(compiler, "Cannot use 'this' outside of a method.");
return;
}
loadThis(compiler);
}
static void subscript(Compiler* compiler, bool canAssign)
{
Signature signature = { "", 0, SIG_SUBSCRIPT, 0 };
finishArgumentList(compiler, &signature);
consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after arguments.");
allowLineBeforeDot(compiler);
if (canAssign && match(compiler, TOKEN_EQ))
{
signature.type = SIG_SUBSCRIPT_SETTER;
validateNumParameters(compiler, ++signature.arity);
expression(compiler);
}
callSignature(compiler, CODE_CALL_0, &signature);
}
static void call(Compiler* compiler, bool canAssign)
{
ignoreNewlines(compiler);
consume(compiler, TOKEN_NAME, "Expect method name after '.'.");
namedCall(compiler, canAssign, CODE_CALL_0);
}
static void and_(Compiler* compiler, bool canAssign)
{
ignoreNewlines(compiler);
int jump = emitJump(compiler, CODE_AND);
parsePrecedence(compiler, PREC_LOGICAL_AND);
patchJump(compiler, jump);
}
static void or_(Compiler* compiler, bool canAssign)
{
ignoreNewlines(compiler);
int jump = emitJump(compiler, CODE_OR);
parsePrecedence(compiler, PREC_LOGICAL_OR);
patchJump(compiler, jump);
}
static void conditional(Compiler* compiler, bool canAssign)
{
ignoreNewlines(compiler);
int ifJump = emitJump(compiler, CODE_JUMP_IF);
parsePrecedence(compiler, PREC_CONDITIONAL);
consume(compiler, TOKEN_COLON,
"Expect ':' after then branch of conditional operator.");
ignoreNewlines(compiler);
int elseJump = emitJump(compiler, CODE_JUMP);
patchJump(compiler, ifJump);
parsePrecedence(compiler, PREC_ASSIGNMENT);
patchJump(compiler, elseJump);
}
void infixOp(Compiler* compiler, bool canAssign)
{
GrammarRule* rule = getRule(compiler->parser->previous.type);
ignoreNewlines(compiler);
parsePrecedence(compiler, (Precedence)(rule->precedence + 1));
Signature signature = { rule->name, (int)strlen(rule->name), SIG_METHOD, 1 };
callSignature(compiler, CODE_CALL_0, &signature);
}
void infixSignature(Compiler* compiler, Signature* signature)
{
signature->type = SIG_METHOD;
signature->arity = 1;
consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after operator name.");
declareNamedVariable(compiler);
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameter name.");
}
void unarySignature(Compiler* compiler, Signature* signature)
{
signature->type = SIG_GETTER;
}
void mixedSignature(Compiler* compiler, Signature* signature)
{
signature->type = SIG_GETTER;
if (match(compiler, TOKEN_LEFT_PAREN))
{
signature->type = SIG_METHOD;
signature->arity = 1;
declareNamedVariable(compiler);
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameter name.");
}
}
static bool maybeSetter(Compiler* compiler, Signature* signature)
{
if (!match(compiler, TOKEN_EQ)) return false;
if (signature->type == SIG_SUBSCRIPT)
{
signature->type = SIG_SUBSCRIPT_SETTER;
}
else
{
signature->type = SIG_SETTER;
}
consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after '='.");
declareNamedVariable(compiler);
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameter name.");
signature->arity++;
return true;
}
void subscriptSignature(Compiler* compiler, Signature* signature)
{
signature->type = SIG_SUBSCRIPT;
signature->length = 0;
finishParameterList(compiler, signature);
consume(compiler, TOKEN_RIGHT_BRACKET, "Expect ']' after parameters.");
maybeSetter(compiler, signature);
}
static void parameterList(Compiler* compiler, Signature* signature)
{
if (!match(compiler, TOKEN_LEFT_PAREN)) return;
signature->type = SIG_METHOD;
ignoreNewlines(compiler);
if (match(compiler, TOKEN_RIGHT_PAREN)) return;
finishParameterList(compiler, signature);
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameters.");
}
void namedSignature(Compiler* compiler, Signature* signature)
{
signature->type = SIG_GETTER;
if (maybeSetter(compiler, signature)) return;
parameterList(compiler, signature);
}
void constructorSignature(Compiler* compiler, Signature* signature)
{
consume(compiler, TOKEN_NAME, "Expect constructor name after 'construct'.");
*signature = signatureFromToken(compiler, SIG_INITIALIZER);
if (match(compiler, TOKEN_EQ))
{
error(compiler, "A constructor cannot be a setter.");
}
if (!match(compiler, TOKEN_LEFT_PAREN))
{
error(compiler, "A constructor cannot be a getter.");
return;
}
if (match(compiler, TOKEN_RIGHT_PAREN)) return;
finishParameterList(compiler, signature);
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after parameters.");
}
#define UNUSED { NULL, NULL, NULL, PREC_NONE, NULL }
#define PREFIX(fn) { fn, NULL, NULL, PREC_NONE, NULL }
#define INFIX(prec, fn) { NULL, fn, NULL, prec, NULL }
#define INFIX_OPERATOR(prec, name) { NULL, infixOp, infixSignature, prec, name }
#define PREFIX_OPERATOR(name) { unaryOp, NULL, unarySignature, PREC_NONE, name }
#define OPERATOR(name) { unaryOp, infixOp, mixedSignature, PREC_TERM, name }
GrammarRule rules[] =
{
PREFIX(grouping),
UNUSED,
{ list, subscript, subscriptSignature, PREC_CALL, NULL },
UNUSED,
PREFIX(map),
UNUSED,
UNUSED,
INFIX(PREC_CALL, call),
INFIX_OPERATOR(PREC_RANGE, ".."),
INFIX_OPERATOR(PREC_RANGE, "..."),
UNUSED,
INFIX_OPERATOR(PREC_FACTOR, "*"),
INFIX_OPERATOR(PREC_FACTOR, "/"),
INFIX_OPERATOR(PREC_FACTOR, "%"),
UNUSED,
INFIX_OPERATOR(PREC_TERM, "+"),
OPERATOR("-"),
INFIX_OPERATOR(PREC_BITWISE_SHIFT, "<<"),
INFIX_OPERATOR(PREC_BITWISE_SHIFT, ">>"),
INFIX_OPERATOR(PREC_BITWISE_OR, "|"),
INFIX(PREC_LOGICAL_OR, or_),
INFIX_OPERATOR(PREC_BITWISE_XOR, "^"),
INFIX_OPERATOR(PREC_BITWISE_AND, "&"),
INFIX(PREC_LOGICAL_AND, and_),
PREFIX_OPERATOR("!"),
PREFIX_OPERATOR("~"),
INFIX(PREC_ASSIGNMENT, conditional),
UNUSED,
INFIX_OPERATOR(PREC_COMPARISON, "<"),
INFIX_OPERATOR(PREC_COMPARISON, ">"),
INFIX_OPERATOR(PREC_COMPARISON, "<="),
INFIX_OPERATOR(PREC_COMPARISON, ">="),
INFIX_OPERATOR(PREC_EQUALITY, "=="),
INFIX_OPERATOR(PREC_EQUALITY, "!="),
UNUSED,
UNUSED,
UNUSED,
{ NULL, NULL, constructorSignature, PREC_NONE, NULL },
UNUSED,
PREFIX(boolean),
UNUSED,
UNUSED,
UNUSED,
UNUSED,
UNUSED,
UNUSED,
INFIX_OPERATOR(PREC_IS, "is"),
PREFIX(null),
UNUSED,
UNUSED,
PREFIX(super_),
PREFIX(this_),
PREFIX(boolean),
UNUSED,
UNUSED,
PREFIX(field),
PREFIX(staticField),
{ name, NULL, namedSignature, PREC_NONE, NULL },
PREFIX(literal),
PREFIX(literal),
PREFIX(stringInterpolation),
UNUSED,
UNUSED,
UNUSED
};
static GrammarRule* getRule(TokenType type)
{
return &rules[type];
}
void parsePrecedence(Compiler* compiler, Precedence precedence)
{
nextToken(compiler->parser);
GrammarFn prefix = rules[compiler->parser->previous.type].prefix;
if (prefix == NULL)
{
error(compiler, "Expected expression.");
return;
}
bool canAssign = precedence <= PREC_CONDITIONAL;
prefix(compiler, canAssign);
while (precedence <= rules[compiler->parser->current.type].precedence)
{
nextToken(compiler->parser);
GrammarFn infix = rules[compiler->parser->previous.type].infix;
infix(compiler, canAssign);
}
}
void expression(Compiler* compiler)
{
parsePrecedence(compiler, PREC_LOWEST);
}
static int getByteCountForArguments(const uint8_t* bytecode,
const Value* constants, int ip)
{
Code instruction = (Code)bytecode[ip];
switch (instruction)
{
case CODE_NULL:
case CODE_FALSE:
case CODE_TRUE:
case CODE_POP:
case CODE_CLOSE_UPVALUE:
case CODE_RETURN:
case CODE_END:
case CODE_LOAD_LOCAL_0:
case CODE_LOAD_LOCAL_1:
case CODE_LOAD_LOCAL_2:
case CODE_LOAD_LOCAL_3:
case CODE_LOAD_LOCAL_4:
case CODE_LOAD_LOCAL_5:
case CODE_LOAD_LOCAL_6:
case CODE_LOAD_LOCAL_7:
case CODE_LOAD_LOCAL_8:
case CODE_CONSTRUCT:
case CODE_FOREIGN_CONSTRUCT:
case CODE_FOREIGN_CLASS:
case CODE_END_MODULE:
case CODE_END_CLASS:
return 0;
case CODE_LOAD_LOCAL:
case CODE_STORE_LOCAL:
case CODE_LOAD_UPVALUE:
case CODE_STORE_UPVALUE:
case CODE_LOAD_FIELD_THIS:
case CODE_STORE_FIELD_THIS:
case CODE_LOAD_FIELD:
case CODE_STORE_FIELD:
case CODE_CLASS:
return 1;
case CODE_CONSTANT:
case CODE_LOAD_MODULE_VAR:
case CODE_STORE_MODULE_VAR:
case CODE_CALL_0:
case CODE_CALL_1:
case CODE_CALL_2:
case CODE_CALL_3:
case CODE_CALL_4:
case CODE_CALL_5:
case CODE_CALL_6:
case CODE_CALL_7:
case CODE_CALL_8:
case CODE_CALL_9:
case CODE_CALL_10:
case CODE_CALL_11:
case CODE_CALL_12:
case CODE_CALL_13:
case CODE_CALL_14:
case CODE_CALL_15:
case CODE_CALL_16:
case CODE_JUMP:
case CODE_LOOP:
case CODE_JUMP_IF:
case CODE_AND:
case CODE_OR:
case CODE_METHOD_INSTANCE:
case CODE_METHOD_STATIC:
case CODE_IMPORT_MODULE:
case CODE_IMPORT_VARIABLE:
return 2;
case CODE_SUPER_0:
case CODE_SUPER_1:
case CODE_SUPER_2:
case CODE_SUPER_3:
case CODE_SUPER_4:
case CODE_SUPER_5:
case CODE_SUPER_6:
case CODE_SUPER_7:
case CODE_SUPER_8:
case CODE_SUPER_9:
case CODE_SUPER_10:
case CODE_SUPER_11:
case CODE_SUPER_12:
case CODE_SUPER_13:
case CODE_SUPER_14:
case CODE_SUPER_15:
case CODE_SUPER_16:
return 4;
case CODE_CLOSURE:
{
int constant = (bytecode[ip + 1] << 8) | bytecode[ip + 2];
ObjFn* loadedFn = AS_FN(constants[constant]);
return 2 + (loadedFn->numUpvalues * 2);
}
}
UNREACHABLE();
return 0;
}
static void startLoop(Compiler* compiler, Loop* loop)
{
loop->enclosing = compiler->loop;
loop->start = compiler->fn->code.count - 1;
loop->scopeDepth = compiler->scopeDepth;
compiler->loop = loop;
}
static void testExitLoop(Compiler* compiler)
{
compiler->loop->exitJump = emitJump(compiler, CODE_JUMP_IF);
}
static void loopBody(Compiler* compiler)
{
compiler->loop->body = compiler->fn->code.count;
statement(compiler);
}
static void endLoop(Compiler* compiler)
{
int loopOffset = compiler->fn->code.count - compiler->loop->start + 2;
emitShortArg(compiler, CODE_LOOP, loopOffset);
patchJump(compiler, compiler->loop->exitJump);
int i = compiler->loop->body;
while (i < compiler->fn->code.count)
{
if (compiler->fn->code.data[i] == CODE_END)
{
compiler->fn->code.data[i] = CODE_JUMP;
patchJump(compiler, i + 1);
i += 3;
}
else
{
i += 1 + getByteCountForArguments(compiler->fn->code.data,
compiler->fn->constants.data, i);
}
}
compiler->loop = compiler->loop->enclosing;
}
static void forStatement(Compiler* compiler)
{
pushScope(compiler);
consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'for'.");
consume(compiler, TOKEN_NAME, "Expect for loop variable name.");
const char* name = compiler->parser->previous.start;
int length = compiler->parser->previous.length;
consume(compiler, TOKEN_IN, "Expect 'in' after loop variable.");
ignoreNewlines(compiler);
expression(compiler);
if (compiler->numLocals + 2 > MAX_LOCALS)
{
error(compiler, "Cannot declare more than %d variables in one scope. (Not enough space for for-loops internal variables)",
MAX_LOCALS);
return;
}
int seqSlot = addLocal(compiler, "seq ", 4);
null(compiler, false);
int iterSlot = addLocal(compiler, "iter ", 5);
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after loop expression.");
Loop loop;
startLoop(compiler, &loop);
loadLocal(compiler, seqSlot);
loadLocal(compiler, iterSlot);
callMethod(compiler, 1, "iterate(_)", 10);
emitByteArg(compiler, CODE_STORE_LOCAL, iterSlot);
testExitLoop(compiler);
loadLocal(compiler, seqSlot);
loadLocal(compiler, iterSlot);
callMethod(compiler, 1, "iteratorValue(_)", 16);
pushScope(compiler);
addLocal(compiler, name, length);
loopBody(compiler);
popScope(compiler);
endLoop(compiler);
popScope(compiler);
}
static void ifStatement(Compiler* compiler)
{
consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'if'.");
expression(compiler);
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after if condition.");
int ifJump = emitJump(compiler, CODE_JUMP_IF);
statement(compiler);
if (match(compiler, TOKEN_ELSE))
{
int elseJump = emitJump(compiler, CODE_JUMP);
patchJump(compiler, ifJump);
statement(compiler);
patchJump(compiler, elseJump);
}
else
{
patchJump(compiler, ifJump);
}
}
static void whileStatement(Compiler* compiler)
{
Loop loop;
startLoop(compiler, &loop);
consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'while'.");
expression(compiler);
consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after while condition.");
testExitLoop(compiler);
loopBody(compiler);
endLoop(compiler);
}
void statement(Compiler* compiler)
{
if (match(compiler, TOKEN_BREAK))
{
if (compiler->loop == NULL)
{
error(compiler, "Cannot use 'break' outside of a loop.");
return;
}
discardLocals(compiler, compiler->loop->scopeDepth + 1);
emitJump(compiler, CODE_END);
}
else if (match(compiler, TOKEN_CONTINUE))
{
if (compiler->loop == NULL)
{
error(compiler, "Cannot use 'continue' outside of a loop.");
return;
}
discardLocals(compiler, compiler->loop->scopeDepth + 1);
int loopOffset = compiler->fn->code.count - compiler->loop->start + 2;
emitShortArg(compiler, CODE_LOOP, loopOffset);
}
else if (match(compiler, TOKEN_FOR))
{
forStatement(compiler);
}
else if (match(compiler, TOKEN_IF))
{
ifStatement(compiler);
}
else if (match(compiler, TOKEN_RETURN))
{
if (peek(compiler) == TOKEN_LINE)
{
Code result = compiler->isInitializer ? CODE_LOAD_LOCAL_0 : CODE_NULL;
emitOp(compiler, result);
}
else
{
if (compiler->isInitializer)
{
error(compiler, "A constructor cannot return a value.");
}
expression(compiler);
}
emitOp(compiler, CODE_RETURN);
}
else if (match(compiler, TOKEN_WHILE))
{
whileStatement(compiler);
}
else if (match(compiler, TOKEN_LEFT_BRACE))
{
pushScope(compiler);
if (finishBlock(compiler))
{
emitOp(compiler, CODE_POP);
}
popScope(compiler);
}
else
{
expression(compiler);
emitOp(compiler, CODE_POP);
}
}
static void createConstructor(Compiler* compiler, Signature* signature,
int initializerSymbol)
{
Compiler methodCompiler;
initCompiler(&methodCompiler, compiler->parser, compiler, true);
emitOp(&methodCompiler, compiler->enclosingClass->isForeign
? CODE_FOREIGN_CONSTRUCT : CODE_CONSTRUCT);
emitShortArg(&methodCompiler, (Code)(CODE_CALL_0 + signature->arity),
initializerSymbol);
emitOp(&methodCompiler, CODE_RETURN);
endCompiler(&methodCompiler, "", 0);
}
static void defineMethod(Compiler* compiler, Variable classVariable,
bool isStatic, int methodSymbol)
{
loadVariable(compiler, classVariable);
Code instruction = isStatic ? CODE_METHOD_STATIC : CODE_METHOD_INSTANCE;
emitShortArg(compiler, instruction, methodSymbol);
}
static int declareMethod(Compiler* compiler, Signature* signature,
const char* name, int length)
{
int symbol = signatureSymbol(compiler, signature);
ClassInfo* classInfo = compiler->enclosingClass;
IntBuffer* methods = classInfo->inStatic
? &classInfo->staticMethods : &classInfo->methods;
for (int i = 0; i < methods->count; i++)
{
if (methods->data[i] == symbol)
{
const char* staticPrefix = classInfo->inStatic ? "static " : "";
error(compiler, "Class %s already defines a %smethod '%s'.",
&compiler->enclosingClass->name->value, staticPrefix, name);
break;
}
}
wrenIntBufferWrite(compiler->parser->vm, methods, symbol);
return symbol;
}
static Value consumeLiteral(Compiler* compiler, const char* message)
{
if(match(compiler, TOKEN_FALSE)) return FALSE_VAL;
if(match(compiler, TOKEN_TRUE)) return TRUE_VAL;
if(match(compiler, TOKEN_NUMBER)) return compiler->parser->previous.value;
if(match(compiler, TOKEN_STRING)) return compiler->parser->previous.value;
if(match(compiler, TOKEN_NAME)) return compiler->parser->previous.value;
error(compiler, message);
nextToken(compiler->parser);
return NULL_VAL;
}
static bool matchAttribute(Compiler* compiler) {
if(match(compiler, TOKEN_HASH))
{
compiler->numAttributes++;
bool runtimeAccess = match(compiler, TOKEN_BANG);
if(match(compiler, TOKEN_NAME))
{
Value group = compiler->parser->previous.value;
TokenType ahead = peek(compiler);
if(ahead == TOKEN_EQ || ahead == TOKEN_LINE)
{
Value key = group;
Value value = NULL_VAL;
if(match(compiler, TOKEN_EQ))
{
value = consumeLiteral(compiler, "Expect a Bool, Num, String or Identifier literal for an attribute value.");
}
if(runtimeAccess) addToAttributeGroup(compiler, NULL_VAL, key, value);
}
else if(match(compiler, TOKEN_LEFT_PAREN))
{
ignoreNewlines(compiler);
if(match(compiler, TOKEN_RIGHT_PAREN))
{
error(compiler, "Expected attributes in group, group cannot be empty.");
}
else
{
while(peek(compiler) != TOKEN_RIGHT_PAREN)
{
consume(compiler, TOKEN_NAME, "Expect name for attribute key.");
Value key = compiler->parser->previous.value;
Value value = NULL_VAL;
if(match(compiler, TOKEN_EQ))
{
value = consumeLiteral(compiler, "Expect a Bool, Num, String or Identifier literal for an attribute value.");
}
if(runtimeAccess) addToAttributeGroup(compiler, group, key, value);
ignoreNewlines(compiler);
if(!match(compiler, TOKEN_COMMA)) break;
ignoreNewlines(compiler);
}
ignoreNewlines(compiler);
consume(compiler, TOKEN_RIGHT_PAREN,
"Expected ')' after grouped attributes.");
}
}
else
{
error(compiler, "Expect an equal, newline or grouping after an attribute key.");
}
}
else
{
error(compiler, "Expect an attribute definition after #.");
}
consumeLine(compiler, "Expect newline after attribute.");
return true;
}
return false;
}
static bool method(Compiler* compiler, Variable classVariable)
{
if(matchAttribute(compiler)) {
return method(compiler, classVariable);
}
bool isForeign = match(compiler, TOKEN_FOREIGN);
bool isStatic = match(compiler, TOKEN_STATIC);
compiler->enclosingClass->inStatic = isStatic;
SignatureFn signatureFn = rules[compiler->parser->current.type].method;
nextToken(compiler->parser);
if (signatureFn == NULL)
{
error(compiler, "Expect method definition.");
return false;
}
Signature signature = signatureFromToken(compiler, SIG_GETTER);
compiler->enclosingClass->signature = &signature;
Compiler methodCompiler;
initCompiler(&methodCompiler, compiler->parser, compiler, true);
signatureFn(&methodCompiler, &signature);
methodCompiler.isInitializer = signature.type == SIG_INITIALIZER;
if (isStatic && signature.type == SIG_INITIALIZER)
{
error(compiler, "A constructor cannot be static.");
}
char fullSignature[MAX_METHOD_SIGNATURE];
int length;
signatureToString(&signature, fullSignature, &length);
copyMethodAttributes(compiler, isForeign, isStatic, fullSignature, length);
int methodSymbol = declareMethod(compiler, &signature, fullSignature, length);
if (isForeign)
{
emitConstant(compiler, wrenNewStringLength(compiler->parser->vm,
fullSignature, length));
methodCompiler.parser->vm->compiler = methodCompiler.parent;
}
else
{
consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' to begin method body.");
finishBody(&methodCompiler);
endCompiler(&methodCompiler, fullSignature, length);
}
defineMethod(compiler, classVariable, isStatic, methodSymbol);
if (signature.type == SIG_INITIALIZER)
{
signature.type = SIG_METHOD;
int constructorSymbol = signatureSymbol(compiler, &signature);
createConstructor(compiler, &signature, methodSymbol);
defineMethod(compiler, classVariable, true, constructorSymbol);
}
return true;
}
static void classDefinition(Compiler* compiler, bool isForeign)
{
Variable classVariable;
classVariable.scope = compiler->scopeDepth == -1 ? SCOPE_MODULE : SCOPE_LOCAL;
classVariable.index = declareNamedVariable(compiler);
Value classNameString = wrenNewStringLength(compiler->parser->vm,
compiler->parser->previous.start, compiler->parser->previous.length);
ObjString* className = AS_STRING(classNameString);
emitConstant(compiler, classNameString);
if (match(compiler, TOKEN_IS))
{
parsePrecedence(compiler, PREC_CALL);
}
else
{
loadCoreVariable(compiler, "Object");
}
int numFieldsInstruction = -1;
if (isForeign)
{
emitOp(compiler, CODE_FOREIGN_CLASS);
}
else
{
numFieldsInstruction = emitByteArg(compiler, CODE_CLASS, 255);
}
defineVariable(compiler, classVariable.index);
pushScope(compiler);
ClassInfo classInfo;
classInfo.isForeign = isForeign;
classInfo.name = className;
classInfo.classAttributes = compiler->attributes->count > 0
? wrenNewMap(compiler->parser->vm)
: NULL;
classInfo.methodAttributes = NULL;
copyAttributes(compiler, classInfo.classAttributes);
wrenSymbolTableInit(&classInfo.fields);
wrenIntBufferInit(&classInfo.methods);
wrenIntBufferInit(&classInfo.staticMethods);
compiler->enclosingClass = &classInfo;
consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' after class declaration.");
matchLine(compiler);
while (!match(compiler, TOKEN_RIGHT_BRACE))
{
if (!method(compiler, classVariable)) break;
if (match(compiler, TOKEN_RIGHT_BRACE)) break;
consumeLine(compiler, "Expect newline after definition in class.");
}
bool hasAttr = classInfo.classAttributes != NULL ||
classInfo.methodAttributes != NULL;
if(hasAttr) {
emitClassAttributes(compiler, &classInfo);
loadVariable(compiler, classVariable);
emitOp(compiler, CODE_END_CLASS);
}
if (!isForeign)
{
compiler->fn->code.data[numFieldsInstruction] =
(uint8_t)classInfo.fields.count;
}
wrenSymbolTableClear(compiler->parser->vm, &classInfo.fields);
wrenIntBufferClear(compiler->parser->vm, &classInfo.methods);
wrenIntBufferClear(compiler->parser->vm, &classInfo.staticMethods);
compiler->enclosingClass = NULL;
popScope(compiler);
}
static void import(Compiler* compiler)
{
ignoreNewlines(compiler);
consume(compiler, TOKEN_STRING, "Expect a string after 'import'.");
int moduleConstant = addConstant(compiler, compiler->parser->previous.value);
emitShortArg(compiler, CODE_IMPORT_MODULE, moduleConstant);
emitOp(compiler, CODE_POP);
if (!match(compiler, TOKEN_FOR)) return;
do
{
ignoreNewlines(compiler);
consume(compiler, TOKEN_NAME, "Expect variable name.");
Token sourceVariableToken = compiler->parser->previous;
int sourceVariableConstant = addConstant(compiler,
wrenNewStringLength(compiler->parser->vm,
sourceVariableToken.start,
sourceVariableToken.length));
int slot = -1;
if(match(compiler, TOKEN_AS))
{
slot = declareNamedVariable(compiler);
}
else
{
slot = declareVariable(compiler, &sourceVariableToken);
}
emitShortArg(compiler, CODE_IMPORT_VARIABLE, sourceVariableConstant);
defineVariable(compiler, slot);
} while (match(compiler, TOKEN_COMMA));
}
static void variableDefinition(Compiler* compiler)
{
consume(compiler, TOKEN_NAME, "Expect variable name.");
Token nameToken = compiler->parser->previous;
if (match(compiler, TOKEN_EQ))
{
ignoreNewlines(compiler);
expression(compiler);
}
else
{
null(compiler, false);
}
int symbol = declareVariable(compiler, &nameToken);
defineVariable(compiler, symbol);
}
void definition(Compiler* compiler)
{
if(matchAttribute(compiler)) {
definition(compiler);
return;
}
if (match(compiler, TOKEN_CLASS))
{
classDefinition(compiler, false);
return;
}
else if (match(compiler, TOKEN_FOREIGN))
{
consume(compiler, TOKEN_CLASS, "Expect 'class' after 'foreign'.");
classDefinition(compiler, true);
return;
}
disallowAttributes(compiler);
if (match(compiler, TOKEN_IMPORT))
{
import(compiler);
}
else if (match(compiler, TOKEN_VAR))
{
variableDefinition(compiler);
}
else
{
statement(compiler);
}
}
ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source,
bool isExpression, bool printErrors)
{
if (strncmp(source, "\xEF\xBB\xBF", 3) == 0) source += 3;
Parser parser;
parser.vm = vm;
parser.module = module;
parser.source = source;
parser.tokenStart = source;
parser.currentChar = source;
parser.currentLine = 1;
parser.numParens = 0;
parser.next.type = TOKEN_ERROR;
parser.next.start = source;
parser.next.length = 0;
parser.next.line = 0;
parser.next.value = UNDEFINED_VAL;
parser.printErrors = printErrors;
parser.hasError = false;
nextToken(&parser);
nextToken(&parser);
int numExistingVariables = module->variables.count;
Compiler compiler;
initCompiler(&compiler, &parser, NULL, false);
ignoreNewlines(&compiler);
if (isExpression)
{
expression(&compiler);
consume(&compiler, TOKEN_EOF, "Expect end of expression.");
}
else
{
while (!match(&compiler, TOKEN_EOF))
{
definition(&compiler);
if (!matchLine(&compiler))
{
consume(&compiler, TOKEN_EOF, "Expect end of file.");
break;
}
}
emitOp(&compiler, CODE_END_MODULE);
}
emitOp(&compiler, CODE_RETURN);
for (int i = numExistingVariables; i < parser.module->variables.count; i++)
{
if (IS_NUM(parser.module->variables.data[i]))
{
parser.previous.type = TOKEN_NAME;
parser.previous.start = parser.module->variableNames.data[i]->value;
parser.previous.length = parser.module->variableNames.data[i]->length;
parser.previous.line = (int)AS_NUM(parser.module->variables.data[i]);
error(&compiler, "Variable is used but not defined.");
}
}
return endCompiler(&compiler, "(script)", 8);
}
void wrenBindMethodCode(ObjClass* classObj, ObjFn* fn)
{
int ip = 0;
for (;;)
{
Code instruction = (Code)fn->code.data[ip];
switch (instruction)
{
case CODE_LOAD_FIELD:
case CODE_STORE_FIELD:
case CODE_LOAD_FIELD_THIS:
case CODE_STORE_FIELD_THIS:
fn->code.data[ip + 1] += classObj->superclass->numFields;
break;
case CODE_SUPER_0:
case CODE_SUPER_1:
case CODE_SUPER_2:
case CODE_SUPER_3:
case CODE_SUPER_4:
case CODE_SUPER_5:
case CODE_SUPER_6:
case CODE_SUPER_7:
case CODE_SUPER_8:
case CODE_SUPER_9:
case CODE_SUPER_10:
case CODE_SUPER_11:
case CODE_SUPER_12:
case CODE_SUPER_13:
case CODE_SUPER_14:
case CODE_SUPER_15:
case CODE_SUPER_16:
{
int constant = (fn->code.data[ip + 3] << 8) | fn->code.data[ip + 4];
fn->constants.data[constant] = OBJ_VAL(classObj->superclass);
break;
}
case CODE_CLOSURE:
{
int constant = (fn->code.data[ip + 1] << 8) | fn->code.data[ip + 2];
wrenBindMethodCode(classObj, AS_FN(fn->constants.data[constant]));
break;
}
case CODE_END:
return;
default:
break;
}
ip += 1 + getByteCountForArguments(fn->code.data, fn->constants.data, ip);
}
}
void wrenMarkCompiler(WrenVM* vm, Compiler* compiler)
{
wrenGrayValue(vm, compiler->parser->current.value);
wrenGrayValue(vm, compiler->parser->previous.value);
wrenGrayValue(vm, compiler->parser->next.value);
do
{
wrenGrayObj(vm, (Obj*)compiler->fn);
wrenGrayObj(vm, (Obj*)compiler->constants);
wrenGrayObj(vm, (Obj*)compiler->attributes);
if (compiler->enclosingClass != NULL)
{
wrenBlackenSymbolTable(vm, &compiler->enclosingClass->fields);
if(compiler->enclosingClass->methodAttributes != NULL)
{
wrenGrayObj(vm, (Obj*)compiler->enclosingClass->methodAttributes);
}
if(compiler->enclosingClass->classAttributes != NULL)
{
wrenGrayObj(vm, (Obj*)compiler->enclosingClass->classAttributes);
}
}
compiler = compiler->parent;
}
while (compiler != NULL);
}
static void disallowAttributes(Compiler* compiler)
{
if (compiler->numAttributes > 0)
{
error(compiler, "Attributes can only specified before a class or a method");
wrenMapClear(compiler->parser->vm, compiler->attributes);
compiler->numAttributes = 0;
}
}
static void addToAttributeGroup(Compiler* compiler,
Value group, Value key, Value value)
{
WrenVM* vm = compiler->parser->vm;
if(IS_OBJ(group)) wrenPushRoot(vm, AS_OBJ(group));
if(IS_OBJ(key)) wrenPushRoot(vm, AS_OBJ(key));
if(IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value));
Value groupMapValue = wrenMapGet(compiler->attributes, group);
if(IS_UNDEFINED(groupMapValue))
{
groupMapValue = OBJ_VAL(wrenNewMap(vm));
wrenMapSet(vm, compiler->attributes, group, groupMapValue);
}
ObjMap* groupMap = AS_MAP(groupMapValue);
Value keyItemsValue = wrenMapGet(groupMap, key);
if(IS_UNDEFINED(keyItemsValue))
{
keyItemsValue = OBJ_VAL(wrenNewList(vm, 0));
wrenMapSet(vm, groupMap, key, keyItemsValue);
}
ObjList* keyItems = AS_LIST(keyItemsValue);
wrenValueBufferWrite(vm, &keyItems->elements, value);
if(IS_OBJ(group)) wrenPopRoot(vm);
if(IS_OBJ(key)) wrenPopRoot(vm);
if(IS_OBJ(value)) wrenPopRoot(vm);
}
static void emitAttributes(Compiler* compiler, ObjMap* attributes)
{
loadCoreVariable(compiler, "Map");
callMethod(compiler, 0, "new()", 5);
for(uint32_t groupIdx = 0; groupIdx < attributes->capacity; groupIdx++)
{
const MapEntry* groupEntry = &attributes->entries[groupIdx];
if(IS_UNDEFINED(groupEntry->key)) continue;
emitConstant(compiler, groupEntry->key);
loadCoreVariable(compiler, "Map");
callMethod(compiler, 0, "new()", 5);
ObjMap* groupItems = AS_MAP(groupEntry->value);
for(uint32_t itemIdx = 0; itemIdx < groupItems->capacity; itemIdx++)
{
const MapEntry* itemEntry = &groupItems->entries[itemIdx];
if(IS_UNDEFINED(itemEntry->key)) continue;
emitConstant(compiler, itemEntry->key);
loadCoreVariable(compiler, "List");
callMethod(compiler, 0, "new()", 5);
ObjList* items = AS_LIST(itemEntry->value);
for(int itemIdx = 0; itemIdx < items->elements.count; ++itemIdx)
{
emitConstant(compiler, items->elements.data[itemIdx]);
callMethod(compiler, 1, "addCore_(_)", 11);
}
callMethod(compiler, 2, "addCore_(_,_)", 13);
}
callMethod(compiler, 2, "addCore_(_,_)", 13);
}
}
static void emitAttributeMethods(Compiler* compiler, ObjMap* attributes)
{
loadCoreVariable(compiler, "Map");
callMethod(compiler, 0, "new()", 5);
for(uint32_t methodIdx = 0; methodIdx < attributes->capacity; methodIdx++)
{
const MapEntry* methodEntry = &attributes->entries[methodIdx];
if(IS_UNDEFINED(methodEntry->key)) continue;
emitConstant(compiler, methodEntry->key);
ObjMap* attributeMap = AS_MAP(methodEntry->value);
emitAttributes(compiler, attributeMap);
callMethod(compiler, 2, "addCore_(_,_)", 13);
}
}
static void emitClassAttributes(Compiler* compiler, ClassInfo* classInfo)
{
loadCoreVariable(compiler, "ClassAttributes");
classInfo->classAttributes
? emitAttributes(compiler, classInfo->classAttributes)
: null(compiler, false);
classInfo->methodAttributes
? emitAttributeMethods(compiler, classInfo->methodAttributes)
: null(compiler, false);
callMethod(compiler, 2, "new(_,_)", 8);
}
static void copyAttributes(Compiler* compiler, ObjMap* into)
{
compiler->numAttributes = 0;
if(compiler->attributes->count == 0) return;
if(into == NULL) return;
WrenVM* vm = compiler->parser->vm;
for(uint32_t attrIdx = 0; attrIdx < compiler->attributes->capacity; attrIdx++)
{
const MapEntry* attrEntry = &compiler->attributes->entries[attrIdx];
if(IS_UNDEFINED(attrEntry->key)) continue;
wrenMapSet(vm, into, attrEntry->key, attrEntry->value);
}
wrenMapClear(vm, compiler->attributes);
}
static void copyMethodAttributes(Compiler* compiler, bool isForeign,
bool isStatic, const char* fullSignature, int32_t length)
{
compiler->numAttributes = 0;
if(compiler->attributes->count == 0) return;
WrenVM* vm = compiler->parser->vm;
ObjMap* methodAttr = wrenNewMap(vm);
wrenPushRoot(vm, (Obj*)methodAttr);
copyAttributes(compiler, methodAttr);
int32_t fullLength = length;
if(isForeign) fullLength += 8;
if(isStatic) fullLength += 7;
char fullSignatureWithPrefix[MAX_METHOD_SIGNATURE + 8 + 7];
const char* foreignPrefix = isForeign ? "foreign " : "";
const char* staticPrefix = isStatic ? "static " : "";
sprintf(fullSignatureWithPrefix, "%s%s%.*s", foreignPrefix, staticPrefix,
length, fullSignature);
fullSignatureWithPrefix[fullLength] = '\0';
if(compiler->enclosingClass->methodAttributes == NULL) {
compiler->enclosingClass->methodAttributes = wrenNewMap(vm);
}
Value key = wrenNewStringLength(vm, fullSignatureWithPrefix, fullLength);
wrenMapSet(vm, compiler->enclosingClass->methodAttributes, key, OBJ_VAL(methodAttr));
wrenPopRoot(vm);
}