clox/compiler.c
2026-01-13 19:52:23 +00:00

1081 lines
26 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "common.h"
#include "compiler.h"
#include "memory.h"
#include "scanner.h"
#ifdef DEBUG_PRINT_CODE
#include "debug.h"
#endif
typedef struct {
Token current;
Token previous;
bool hadError;
bool panicMode;
} Parser;
typedef enum {
PREC_NONE,
PREC_ASSIGNMENT,
PREC_OR,
PREC_AND,
PREC_EQUALITY,
PREC_COMPARISON,
PREC_TERM,
PREC_FACTOR,
PREC_UNARY,
PREC_CALL,
PREC_PRIMARY,
} Precedence;
typedef void (*ParseFn)(bool canAssign);
typedef struct {
ParseFn prefix;
ParseFn infix;
Precedence precedence;
} ParseRule;
typedef struct {
Token name;
int depth;
bool isCaptured;
} Local;
typedef struct {
uint8_t index;
bool isLocal;
} Upvalue;
typedef enum {
TYPE_FUNCTION,
TYPE_INITIALIZER,
TYPE_METHOD,
TYPE_SCRIPT,
} FunctionType;
typedef struct Compiler {
struct Compiler* enclosing;
ObjFunction* function;
FunctionType type;
Local locals[UINT8_COUNT];
int localCount;
Upvalue upvalues[UINT8_COUNT];
int scopeDepth;
} Compiler;
typedef struct ClassCompiler {
struct ClassCompiler* enclosing;
bool hasSuperclass;
} ClassCompiler;
Parser parser;
Compiler* current = nullptr;
ClassCompiler* currentClass = nullptr;
static Chunk* currentChunk()
{
return &current->function->chunk;
}
static void errorAt(Token* token, const char* message)
{
if (parser.panicMode) {
return;
}
parser.panicMode = true;
fprintf(stderr, "[line %d] Error", token->line);
if (token->type == TOKEN_EOF) {
fprintf(stderr, " at end");
} else if (token->type == TOKEN_ERROR) {
} else {
fprintf(stderr, " at '%.*s'", token->length, token->start);
}
fprintf(stderr, ": %s\n", message);
parser.hadError = true;
}
static void error(const char* message)
{
errorAt(&parser.previous, message);
}
static void errorAtCurrent(const char* message)
{
errorAt(&parser.current, message);
}
static void advance()
{
parser.previous = parser.current;
for (;;) {
parser.current = scanToken();
if (parser.current.type != TOKEN_ERROR) {
break;
}
errorAtCurrent(parser.current.start);
}
}
static void consume(TokenType type, const char* message)
{
if (parser.current.type == type) {
advance();
return;
}
errorAtCurrent(message);
}
static bool check(TokenType type)
{
return parser.current.type == type;
}
static bool match(TokenType type)
{
if (!check(type)) {
return false;
}
advance();
return true;
}
static void emitByte(uint8_t byte)
{
writeChunk(currentChunk(), byte, parser.previous.line);
}
static void emitBytes(uint8_t byte1, uint8_t byte2)
{
emitByte(byte1);
emitByte(byte2);
}
static void emitLoop(int loopStart)
{
emitByte(OP_LOOP);
auto offset = currentChunk()->count - loopStart + 2;
if (offset > UINT16_MAX) {
error("Loop body too large.");
}
emitByte((offset >> 8) & 0xff);
emitByte(offset & 0xff);
}
static int emitJump(uint8_t instruction)
{
emitByte(instruction);
emitByte(0xff);
emitByte(0xff);
return currentChunk()->count - 2;
}
static void emitReturn()
{
if (current->type == TYPE_INITIALIZER) {
emitBytes(OP_GET_LOCAL, 0);
} else {
emitByte(OP_NIL);
}
emitByte(OP_RETURN);
}
static uint8_t makeConstant(Value value)
{
auto constant = addConstant(currentChunk(), value);
if (constant > UINT8_MAX) {
error("Too many constants in one chunk.");
return 0;
}
return (uint8_t)constant;
}
static void emitConstant(Value value)
{
emitBytes(OP_CONSTANT, makeConstant(value));
}
static void patchJump(int offset)
{
auto jump = currentChunk()->count - offset - 2;
if (jump > UINT16_MAX) {
error("Too much code to jump over.");
}
currentChunk()->code[offset] = (jump >> 8) & 0xff;
currentChunk()->code[offset + 1] = jump & 0xff;
}
static void initCompiler(Compiler* compiler, FunctionType type)
{
compiler->enclosing = current;
compiler->function = nullptr;
compiler->type = type;
compiler->localCount = 0;
compiler->scopeDepth = 0;
compiler->function = newFunction();
current = compiler;
if (type != TYPE_SCRIPT) {
current->function->name = copyString(parser.previous.start, parser.previous.length);
}
Local* local = &current->locals[current->localCount++];
local->depth = 0;
local->isCaptured = false;
if (type != TYPE_FUNCTION) {
local->name.start = "this";
local->name.length = 4;
} else {
local->name.start = "";
local->name.length = 0;
}
}
static ObjFunction* endCompiler()
{
emitReturn();
ObjFunction* function = current->function;
#ifdef DEBUG_PRINT_CODE
if (!parser.hadError) {
disassembleChunk(currentChunk(), function->name != nullptr ? function->name->chars : "<script>");
}
#endif
current = current->enclosing;
return function;
}
static void beginScope()
{
current->scopeDepth++;
}
static void endScope()
{
current->scopeDepth--;
while (current->localCount > 0 && current->locals[current->localCount - 1].depth > current->scopeDepth) {
if (current->locals[current->localCount - 1].isCaptured) {
emitByte(OP_CLOSE_UPVALUE);
} else {
emitByte(OP_POP);
}
current->localCount--;
}
}
static void expression();
static void statement();
static void declaration();
static ParseRule* getRule(TokenType type);
static void parsePrecedence(Precedence precedence);
static uint8_t identifierConstant(Token* name)
{
return makeConstant(OBJ_VAL(copyString(name->start, name->length)));
}
static bool identifiersEqual(Token* a, Token* b)
{
if (a->length != b->length) {
return false;
}
return memcmp(a->start, b->start, a->length) == 0;
}
static int resolveLocal(Compiler* compiler, Token* name)
{
for (auto i = compiler->localCount - 1; i >= 0; i--) {
Local* local = &compiler->locals[i];
if (identifiersEqual(name, &local->name)) {
if (local->depth == -1) {
error("Can't read local variable in its own initializer.");
}
return i;
}
}
return -1;
}
static int addUpvalue(Compiler* compiler, uint8_t index, bool isLocal)
{
auto upvalueCount = compiler->function->upvalueCount;
for (auto i = 0; i < upvalueCount; i++) {
Upvalue* upvalue = &compiler->upvalues[i];
if (upvalue->index == index && upvalue->isLocal == isLocal) {
return i;
}
}
if (upvalueCount == UINT8_COUNT) {
error("Too many closure variables in function.");
return 0;
}
compiler->upvalues[upvalueCount].isLocal = isLocal;
compiler->upvalues[upvalueCount].index = index;
return compiler->function->upvalueCount++;
}
static int resolveUpvalue(Compiler* compiler, Token* name)
{
if (compiler->enclosing == nullptr) {
return -1;
}
auto local = resolveLocal(compiler->enclosing, name);
if (local != -1) {
compiler->enclosing->locals[local].isCaptured = true;
return addUpvalue(compiler, (uint8_t)local, true);
}
auto upvalue = resolveUpvalue(compiler->enclosing, name);
if (upvalue != -1) {
return addUpvalue(compiler, (uint8_t)upvalue, false);
}
return -1;
}
static void addLocal(Token name)
{
if (current->localCount == UINT8_COUNT) {
error("Too many local variables in function.");
return;
}
Local* local = &current->locals[current->localCount++];
local->name = name;
local->depth = -1;
local->isCaptured = false;
}
static void declareVariable()
{
if (current->scopeDepth == 0) {
return;
}
Token* name = &parser.previous;
for (auto i = current->localCount - 1; i >= 0; i--) {
Local* local = &current->locals[i];
if (local->depth != -1 && local->depth < current->scopeDepth) {
break;
}
if (identifiersEqual(name, &local->name)) {
error("Already a variable with this name in this scope.");
}
}
addLocal(*name);
}
static uint8_t parseVariable(const char* errorMessage)
{
consume(TOKEN_IDENTIFIER, errorMessage);
declareVariable();
if (current->scopeDepth > 0) {
return 0;
}
return identifierConstant(&parser.previous);
}
static void markInitialized()
{
if (current->scopeDepth == 0) {
return;
}
current->locals[current->localCount - 1].depth = current->scopeDepth;
}
static void defineVariable(uint8_t global)
{
if (current->scopeDepth > 0) {
markInitialized();
return;
}
emitBytes(OP_DEFINE_GLOBAL, global);
}
static uint8_t argumentList()
{
uint8_t argCount = 0;
if (!check(TOKEN_RIGHT_PAREN)) {
do {
expression();
if (argCount == 255) {
error("Can't have more than 255 arguments.");
}
argCount++;
} while (match(TOKEN_COMMA));
}
consume(TOKEN_RIGHT_PAREN, "Expect ')' after arguments.");
return argCount;
}
static void and_(bool canAssign)
{
(void)canAssign;
auto endJump = emitJump(OP_JUMP_IF_FALSE);
emitByte(OP_POP);
parsePrecedence(PREC_AND);
patchJump(endJump);
}
static void binary(bool canAssign)
{
(void)canAssign;
auto operatorType = parser.previous.type;
ParseRule* rule = getRule(operatorType);
parsePrecedence((Precedence)(rule->precedence + 1));
switch (operatorType) {
case TOKEN_BANG_EQUAL:
emitBytes(OP_EQUAL, OP_NOT);
break;
case TOKEN_EQUAL_EQUAL:
emitByte(OP_EQUAL);
break;
case TOKEN_GREATER:
emitByte(OP_GREATER);
break;
case TOKEN_GREATER_EQUAL:
emitBytes(OP_LESS, OP_NOT);
break;
case TOKEN_LESS:
emitByte(OP_LESS);
break;
case TOKEN_LESS_EQUAL:
emitBytes(OP_GREATER, OP_NOT);
break;
case TOKEN_PLUS:
emitByte(OP_ADD);
break;
case TOKEN_MINUS:
emitByte(OP_SUBTRACT);
break;
case TOKEN_STAR:
emitByte(OP_MULTIPLY);
break;
case TOKEN_SLASH:
emitByte(OP_DIVIDE);
break;
default:
return;
}
}
static void call(bool canAssign)
{
(void)canAssign;
auto argCount = argumentList();
emitBytes(OP_CALL, argCount);
}
static void dot(bool canAssign)
{
consume(TOKEN_IDENTIFIER, "Expect property name after '.'.");
auto name = identifierConstant(&parser.previous);
if (canAssign && match(TOKEN_EQUAL)) {
expression();
emitBytes(OP_SET_PROPERTY, name);
} else if (match(TOKEN_LEFT_PAREN)) {
auto argCount = argumentList();
emitBytes(OP_INVOKE, name);
emitByte(argCount);
} else {
emitBytes(OP_GET_PROPERTY, name);
}
}
static void literal(bool canAssign)
{
(void)canAssign;
switch (parser.previous.type) {
case TOKEN_FALSE:
emitByte(OP_FALSE);
break;
case TOKEN_NIL:
emitByte(OP_NIL);
break;
case TOKEN_TRUE:
emitByte(OP_TRUE);
break;
default:
return;
}
}
static void grouping(bool canAssign)
{
(void)canAssign;
expression();
consume(TOKEN_RIGHT_PAREN, "Expect ')' after expression.");
}
static void number(bool canAssign)
{
(void)canAssign;
auto value = strtod(parser.previous.start, nullptr);
emitConstant(NUMBER_VAL(value));
}
static void or_(bool canAssign)
{
(void)canAssign;
auto elseJump = emitJump(OP_JUMP_IF_FALSE);
auto endJump = emitJump(OP_JUMP);
patchJump(elseJump);
emitByte(OP_POP);
parsePrecedence(PREC_OR);
patchJump(endJump);
}
static void string(bool canAssign)
{
(void)canAssign;
emitConstant(OBJ_VAL(copyString(parser.previous.start + 1, parser.previous.length - 2)));
}
static void namedVariable(Token name, bool canAssign)
{
uint8_t getOp, setOp;
auto arg = resolveLocal(current, &name);
if (arg != -1) {
getOp = OP_GET_LOCAL;
setOp = OP_SET_LOCAL;
} else if ((arg = resolveUpvalue(current, &name)) != -1) {
getOp = OP_GET_UPVALUE;
setOp = OP_GET_UPVALUE;
} else {
arg = identifierConstant(&name);
getOp = OP_GET_GLOBAL;
setOp = OP_SET_GLOBAL;
}
if (canAssign && match(TOKEN_EQUAL)) {
expression();
emitBytes(setOp, arg);
} else {
emitBytes(getOp, arg);
}
}
static void variable(bool canAssign)
{
namedVariable(parser.previous, canAssign);
}
static Token syntheticToken(const char* text)
{
return (Token){
.start = text,
.length = (int)strlen(text),
};
}
static void super_(bool canAssign)
{
(void)canAssign;
if (currentClass == nullptr) {
error("Can't use 'super' outside of a class.");
} else if (!currentClass->hasSuperclass) {
error("Can't use 'super' in a class with no superclass.");
}
consume(TOKEN_DOT, "Expect '.' after 'super'.");
consume(TOKEN_IDENTIFIER, "Expect superclass method name.");
auto name = identifierConstant(&parser.previous);
namedVariable(syntheticToken("this"), false);
if (match(TOKEN_LEFT_PAREN)) {
auto argCount = argumentList();
namedVariable(syntheticToken("super"), false);
emitBytes(OP_SUPER_INVOKE, name);
emitByte(argCount);
} else {
namedVariable(syntheticToken("super"), false);
emitBytes(OP_GET_SUPER, name);
}
}
static void this_(bool canAssign)
{
if (currentClass == nullptr) {
error("Can't use 'this' outside of a class.");
return;
}
(void)canAssign;
variable(false);
}
static void unary(bool canAssign)
{
(void)canAssign;
auto operatorType = parser.previous.type;
parsePrecedence(PREC_UNARY);
switch (operatorType) {
case TOKEN_BANG:
emitByte(OP_NOT);
break;
case TOKEN_MINUS:
emitByte(OP_NEGATE);
break;
default:
return;
}
}
ParseRule rules[] = {
[TOKEN_LEFT_PAREN] = { .prefix = grouping, .infix = call, .precedence = PREC_CALL },
[TOKEN_RIGHT_PAREN] = { .prefix = nullptr, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_LEFT_BRACE] = { .prefix = nullptr, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_RIGHT_BRACE] = { .prefix = nullptr, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_COMMA] = { .prefix = nullptr, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_DOT] = { .prefix = nullptr, .infix = dot, .precedence = PREC_CALL },
[TOKEN_MINUS] = { .prefix = unary, .infix = binary, .precedence = PREC_TERM },
[TOKEN_PLUS] = { .prefix = nullptr, .infix = binary, .precedence = PREC_TERM },
[TOKEN_SEMICOLON] = { .prefix = nullptr, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_SLASH] = { .prefix = nullptr, .infix = binary, .precedence = PREC_FACTOR },
[TOKEN_STAR] = { .prefix = nullptr, .infix = binary, .precedence = PREC_FACTOR },
[TOKEN_BANG] = { .prefix = unary, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_BANG_EQUAL] = { .prefix = nullptr, .infix = binary, .precedence = PREC_EQUALITY },
[TOKEN_EQUAL] = { .prefix = nullptr, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_EQUAL_EQUAL] = { .prefix = nullptr, .infix = binary, .precedence = PREC_EQUALITY },
[TOKEN_GREATER] = { .prefix = nullptr, .infix = binary, .precedence = PREC_COMPARISON },
[TOKEN_GREATER_EQUAL] = { .prefix = nullptr, .infix = binary, .precedence = PREC_COMPARISON },
[TOKEN_LESS] = { .prefix = nullptr, .infix = binary, .precedence = PREC_COMPARISON },
[TOKEN_LESS_EQUAL] = { .prefix = nullptr, .infix = binary, .precedence = PREC_COMPARISON },
[TOKEN_IDENTIFIER] = { .prefix = variable, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_STRING] = { .prefix = string, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_NUMBER] = { .prefix = number, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_AND] = { .prefix = nullptr, .infix = and_, .precedence = PREC_AND },
[TOKEN_CLASS] = { .prefix = nullptr, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_ELSE] = { .prefix = nullptr, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_FALSE] = { .prefix = literal, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_FOR] = { .prefix = nullptr, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_FUN] = { .prefix = nullptr, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_IF] = { .prefix = nullptr, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_NIL] = { .prefix = literal, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_OR] = { .prefix = nullptr, .infix = or_, .precedence = PREC_OR },
[TOKEN_PRINT] = { .prefix = nullptr, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_RETURN] = { .prefix = nullptr, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_SUPER] = { .prefix = super_, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_THIS] = { .prefix = this_, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_TRUE] = { .prefix = literal, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_VAR] = { .prefix = nullptr, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_WHILE] = { .prefix = nullptr, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_ERROR] = { .prefix = nullptr, .infix = nullptr, .precedence = PREC_NONE },
[TOKEN_EOF] = { .prefix = nullptr, .infix = nullptr, .precedence = PREC_NONE },
};
static void parsePrecedence(Precedence precedence)
{
advance();
auto prefixRule = getRule(parser.previous.type)->prefix;
if (prefixRule == nullptr) {
error("Expected expression.");
return;
}
auto canAssign = precedence <= PREC_ASSIGNMENT;
prefixRule(canAssign);
while (precedence < getRule(parser.current.type)->precedence) {
advance();
auto infixRule = getRule(parser.previous.type)->infix;
infixRule(canAssign);
}
if (canAssign && match(TOKEN_EQUAL)) {
error("Invalid assignment target.");
}
}
static ParseRule* getRule(TokenType type)
{
return &rules[type];
}
static void expression()
{
parsePrecedence(PREC_ASSIGNMENT);
}
static void block()
{
while (!check(TOKEN_RIGHT_BRACE) && !check(TOKEN_EOF)) {
declaration();
}
consume(TOKEN_RIGHT_BRACE, "Expect '}' after block.");
}
static void function(FunctionType type)
{
Compiler compiler;
initCompiler(&compiler, type);
beginScope();
consume(TOKEN_LEFT_PAREN, "Expect '(' after function name.");
if (!check(TOKEN_RIGHT_PAREN)) {
do {
current->function->arity++;
if (current->function->arity > 255) {
errorAtCurrent("Can't have more than 255 parameters.");
}
auto constant = parseVariable("Expect parameter name.");
defineVariable(constant);
} while (match(TOKEN_COMMA));
}
consume(TOKEN_RIGHT_PAREN, "Expect ')' after parameters.");
consume(TOKEN_LEFT_BRACE, "Expect '{' before function body.");
block();
ObjFunction* function = endCompiler();
emitBytes(OP_CLOSURE, makeConstant(OBJ_VAL(function)));
for (auto i = 0; i < function->upvalueCount; i++) {
emitByte(compiler.upvalues[i].isLocal ? 1 : 0);
emitByte(compiler.upvalues[i].index);
}
}
static void method()
{
consume(TOKEN_IDENTIFIER, "Expect method name.");
auto constant = identifierConstant(&parser.previous);
FunctionType type = TYPE_METHOD;
if (parser.previous.length == 4 && memcmp(parser.previous.start, "init", 4) == 0) {
type = TYPE_INITIALIZER;
}
function(type);
emitBytes(OP_METHOD, constant);
}
static void classDeclaration()
{
consume(TOKEN_IDENTIFIER, "Expect class name");
auto className = parser.previous;
auto nameConstant = identifierConstant(&parser.previous);
declareVariable();
emitBytes(OP_CLASS, nameConstant);
defineVariable(nameConstant);
ClassCompiler classCompiler;
classCompiler.hasSuperclass = false;
classCompiler.enclosing = currentClass;
currentClass = &classCompiler;
if (match(TOKEN_LESS)) {
consume(TOKEN_IDENTIFIER, "Expect superclass name.");
variable(false);
if (identifiersEqual(&className, &parser.previous)) {
error("A class can't inherit from itself.");
}
beginScope();
addLocal(syntheticToken("super"));
defineVariable(0);
namedVariable(className, false);
emitByte(OP_INHERIT);
classCompiler.hasSuperclass = true;
}
namedVariable(className, false);
consume(TOKEN_LEFT_BRACE, "Expect '{' before class body.");
while (!check(TOKEN_RIGHT_BRACE) && !check(TOKEN_EOF)) {
method();
}
consume(TOKEN_RIGHT_BRACE, "Expect '}' after class body");
emitByte(OP_POP);
if (classCompiler.hasSuperclass) {
endScope();
}
currentClass = currentClass->enclosing;
}
static void funDeclaration()
{
auto global = parseVariable("Expect function name.");
markInitialized();
function(TYPE_FUNCTION);
defineVariable(global);
}
static void varDeclaration()
{
auto global = parseVariable("Expected variable name.");
if (match(TOKEN_EQUAL)) {
expression();
} else {
emitByte(OP_NIL);
}
consume(TOKEN_SEMICOLON, "Expected ';' after variable declaration.");
defineVariable(global);
}
static void expressionStatement()
{
expression();
consume(TOKEN_SEMICOLON, "Expect ';' after expression.");
emitByte(OP_POP);
}
static void forStatement()
{
beginScope();
consume(TOKEN_LEFT_PAREN, "Expect '(' after 'for'.");
if (match(TOKEN_SEMICOLON)) {
// No initializer
} else if (match(TOKEN_VAR)) {
varDeclaration();
} else {
expressionStatement();
}
auto loopStart = currentChunk()->count;
auto exitJump = -1;
if (!match(TOKEN_SEMICOLON)) {
expression();
consume(TOKEN_SEMICOLON, "Expect ';' after loop condition.");
exitJump = emitJump(OP_JUMP_IF_FALSE);
emitByte(OP_POP);
}
if (!match(TOKEN_RIGHT_PAREN)) {
auto bodyJump = emitJump(OP_JUMP);
auto incrementStart = currentChunk()->count;
expression();
emitByte(OP_POP);
consume(TOKEN_RIGHT_PAREN, "Expect ')' after for clauses.");
emitLoop(loopStart);
loopStart = incrementStart;
patchJump(bodyJump);
}
statement();
emitLoop(loopStart);
if (exitJump != -1) {
patchJump(exitJump);
emitByte(OP_POP);
}
endScope();
}
static void ifStatement()
{
consume(TOKEN_LEFT_PAREN, "Expect '(' after 'if'.");
expression();
consume(TOKEN_RIGHT_PAREN, "Expect ')' after condition.");
auto thenJump = emitJump(OP_JUMP_IF_FALSE);
emitByte(OP_POP);
statement();
int elseJump = emitJump(OP_JUMP);
patchJump(thenJump);
emitByte(OP_POP);
if (match(TOKEN_ELSE)) {
statement();
}
patchJump(elseJump);
}
static void printStatement()
{
expression();
consume(TOKEN_SEMICOLON, "Expected ';' after value.");
emitByte(OP_PRINT);
}
static void returnStatement()
{
if (current->type == TYPE_SCRIPT) {
error("Can't return from top-level code.");
}
if (match(TOKEN_SEMICOLON)) {
emitReturn();
} else {
if (current->type == TYPE_INITIALIZER) {
error("Can't return a value from an initializer.");
}
expression();
consume(TOKEN_SEMICOLON, "Expect ';' after return value.");
emitByte(OP_RETURN);
}
}
static void whileStatement()
{
auto loopStart = currentChunk()->count;
consume(TOKEN_LEFT_PAREN, "EXPECT '(' after 'while'.");
expression();
consume(TOKEN_RIGHT_PAREN, "Expect ')' after condition.");
auto exitJump = emitJump(OP_JUMP_IF_FALSE);
emitByte(OP_POP);
statement();
emitLoop(loopStart);
patchJump(exitJump);
emitByte(OP_POP);
}
static void synchronize()
{
parser.panicMode = false;
while (parser.current.type != TOKEN_EOF) {
if (parser.previous.type == TOKEN_SEMICOLON) {
return;
}
switch (parser.current.type) {
case TOKEN_CLASS:
case TOKEN_FUN:
case TOKEN_VAR:
case TOKEN_FOR:
case TOKEN_IF:
case TOKEN_WHILE:
case TOKEN_PRINT:
case TOKEN_RETURN:
return;
default:
;
}
advance();
}
}
static void declaration()
{
if (match(TOKEN_CLASS)) {
classDeclaration();
} else if (match(TOKEN_FUN)) {
funDeclaration();
} else if (match(TOKEN_VAR)) {
varDeclaration();
} else {
statement();
}
if (parser.panicMode) {
synchronize();
}
}
static void statement()
{
if (match(TOKEN_PRINT)) {
printStatement();
} else if (match(TOKEN_IF)) {
ifStatement();
} else if (match(TOKEN_RETURN)) {
returnStatement();
} else if (match(TOKEN_WHILE)) {
whileStatement();
} else if (match(TOKEN_FOR)) {
forStatement();
} else if (match(TOKEN_LEFT_BRACE)) {
beginScope();
block();
endScope();
} else {
expressionStatement();
}
}
ObjFunction* compile(const char* source)
{
initScanner(source);
Compiler compiler;
initCompiler(&compiler, TYPE_SCRIPT);
parser.hadError = false;
parser.panicMode = false;
advance();
while (!match(TOKEN_EOF)) {
declaration();
}
ObjFunction* function = endCompiler();
return parser.hadError ? nullptr : function;
}
void markCompilerRoots()
{
Compiler* compiler = current;
while (compiler != nullptr) {
markObject((Obj*)compiler->function);
compiler = compiler->enclosing;
}
}