diff --git a/clox/clox b/clox/clox new file mode 100755 index 0000000..2c5a0e7 Binary files /dev/null and b/clox/clox differ diff --git a/clox/common.h b/clox/common.h index ddab699..7197be4 100644 --- a/clox/common.h +++ b/clox/common.h @@ -6,5 +6,5 @@ #include #define DEBUG_TRACE_EXECUTION - +#define DEBUG_PRINT_CODE #endif diff --git a/clox/compiler.c b/clox/compiler.c index 5c6ab1e..7915530 100644 --- a/clox/compiler.c +++ b/clox/compiler.c @@ -1,22 +1,246 @@ #include +#include #include "common.h" #include "compiler.h" #include "scanner.h" -void compile(const char* source) { - initScanner(source); - int line = -1; - for (;;) { - Token token = scanToken(); - if (token.line != line) { - printf("%4d ", token.line); - line = token.line; - } else { - printf(" | "); - } - printf("%2d '%.*s'\n", token.type, token.length, token.start); +#ifdef DEBUG_PRINT_CODE +#include "debug.h" +#endif - if (token.type == TOKEN_EOF) break; +typedef struct { + Token current; + Token previous; + bool hadError; + bool panicMode; +} Parser; + +typedef enum { + PREC_NONE, + PREC_ASSIGNMENT, // = + PREC_OR, // or + PREC_AND, // and + PREC_EQUALITY, // == != + PREC_COMPARISON, // < > <= >= + PREC_TERM, // + - + PREC_FACTOR, // * / + PREC_UNARY, // ! - + PREC_CALL, // . () + PREC_PRIMARY +} Precedence; + +typedef void (*ParseFn)(); + +typedef struct { + ParseFn prefix; + ParseFn infix; + Precedence precedence; +} ParseRule; + +Parser parser; +Chunk* compilingChunk; + +static Chunk* currentChunk() { + return compilingChunk; +} + +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) { + // Nothing. + } 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 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 endCompiler() { + emitReturn(); + #ifdef DEBUG_PRINT_CODE + if (!parser.hadError) { + disassembleChunk(currentChunk(), "code"); + } +#endif +} +static void expression(); +static ParseRule* getRule(TokenType type); +static void parsePrecedence(Precedence precedence); + +static void binary() { + TokenType operatorType = parser.previous.type; + ParseRule* rule = getRule(operatorType); + parsePrecedence((Precedence)(rule->precedence + 1)); + + switch (operatorType) { + 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; // Unreachable. + } +} + +static void emitReturn() { + emitByte(OP_RETURN); +} + +static uint8_t makeConstant(Value value) { + int 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 parsePrecedence(Precedence precedence) { + advance(); + ParseFn prefixRule = getRule(parser.previous.type)->prefix; + if (prefixRule == NULL) { + error("Expect expression."); + return; + } + + prefixRule(); + + while (precedence <= getRule(parser.current.type)->precedence) { + advance(); + ParseFn infixRule = getRule(parser.previous.type)->infix; + infixRule(); + } +} + +ParseRule rules[] = { + [TOKEN_LEFT_PAREN] = {grouping, NULL, PREC_NONE}, + [TOKEN_RIGHT_PAREN] = {NULL, NULL, PREC_NONE}, + [TOKEN_LEFT_BRACE] = {NULL, NULL, PREC_NONE}, + [TOKEN_RIGHT_BRACE] = {NULL, NULL, PREC_NONE}, + [TOKEN_COMMA] = {NULL, NULL, PREC_NONE}, + [TOKEN_DOT] = {NULL, NULL, PREC_NONE}, + [TOKEN_MINUS] = {unary, binary, PREC_TERM}, + [TOKEN_PLUS] = {NULL, binary, PREC_TERM}, + [TOKEN_SEMICOLON] = {NULL, NULL, PREC_NONE}, + [TOKEN_SLASH] = {NULL, binary, PREC_FACTOR}, + [TOKEN_STAR] = {NULL, binary, PREC_FACTOR}, + [TOKEN_BANG] = {NULL, NULL, PREC_NONE}, + [TOKEN_BANG_EQUAL] = {NULL, NULL, PREC_NONE}, + [TOKEN_EQUAL] = {NULL, NULL, PREC_NONE}, + [TOKEN_EQUAL_EQUAL] = {NULL, NULL, PREC_NONE}, + [TOKEN_GREATER] = {NULL, NULL, PREC_NONE}, + [TOKEN_GREATER_EQUAL] = {NULL, NULL, PREC_NONE}, + [TOKEN_LESS] = {NULL, NULL, PREC_NONE}, + [TOKEN_LESS_EQUAL] = {NULL, NULL, PREC_NONE}, + [TOKEN_IDENTIFIER] = {NULL, NULL, PREC_NONE}, + [TOKEN_STRING] = {NULL, NULL, PREC_NONE}, + [TOKEN_NUMBER] = {number, NULL, PREC_NONE}, + [TOKEN_AND] = {NULL, NULL, PREC_NONE}, + [TOKEN_CLASS] = {NULL, NULL, PREC_NONE}, + [TOKEN_ELSE] = {NULL, NULL, PREC_NONE}, + [TOKEN_FALSE] = {NULL, NULL, PREC_NONE}, + [TOKEN_FOR] = {NULL, NULL, PREC_NONE}, + [TOKEN_FUN] = {NULL, NULL, PREC_NONE}, + [TOKEN_IF] = {NULL, NULL, PREC_NONE}, + [TOKEN_NIL] = {NULL, NULL, PREC_NONE}, + [TOKEN_OR] = {NULL, NULL, PREC_NONE}, + [TOKEN_PRINT] = {NULL, NULL, PREC_NONE}, + [TOKEN_RETURN] = {NULL, NULL, PREC_NONE}, + [TOKEN_SUPER] = {NULL, NULL, PREC_NONE}, + [TOKEN_THIS] = {NULL, NULL, PREC_NONE}, + [TOKEN_TRUE] = {NULL, NULL, PREC_NONE}, + [TOKEN_VAR] = {NULL, NULL, PREC_NONE}, + [TOKEN_WHILE] = {NULL, NULL, PREC_NONE}, + [TOKEN_ERROR] = {NULL, NULL, PREC_NONE}, + [TOKEN_EOF] = {NULL, NULL, PREC_NONE}, +}; + +static ParseRule* getRule(TokenType type) { + return &rules[type]; +} + +static void expression() { + // What goes here? + parsePrecedence(PREC_ASSIGNMENT); +} +static void grouping() { + expression(); + consume(TOKEN_RIGHT_PAREN, "Expect ')' after expression."); +} + +static void number() { + double value = strtod(parser.previous.start, NULL); + emitConstant(value); +} + +static void unary() { + TokenType operatorType = parser.previous.type; + + // Compile the operand. + parsePrecedence(PREC_UNARY); + // Emit the operator instruction. + switch (operatorType) { + case TOKEN_MINUS: emitByte(OP_NEGATE); break; + default: return; // Unreachable. + } +} + +bool compile(const char* source, Chunk* chunk) { + initScanner(source); + compilingChunk = chunk; + parser.hadError = false; + parser.panicMode = false; + advance(); + expression(); + consume(TOKEN_EOF, "Expect end of expression."); + endCompiler(); + return !parser.hadError; +} diff --git a/clox/compiler.h b/clox/compiler.h index 53bbae0..6e976ad 100644 --- a/clox/compiler.h +++ b/clox/compiler.h @@ -1,7 +1,9 @@ #ifndef clox_compiler_h #define clox_compiler_h -void compile(const char* source); +#include "vm.h" + +bool compile(const char* source, Chunk* chunk); #endif diff --git a/clox/scanner.c b/clox/scanner.c index 3d1c3be..5faddeb 100644 --- a/clox/scanner.c +++ b/clox/scanner.c @@ -18,9 +18,8 @@ void initScanner(const char* source) { scanner.line = 1; } -static Token identifier() { - while (isAlpha(peek()) || isDigit(peek())) advance(); - return makeToken(identifierType()); +static bool isDigit(char c) { + return c >= '0' && c <= '9'; } static bool isAlpha(char c) { @@ -144,6 +143,10 @@ static TokenType identifierType() { return TOKEN_IDENTIFIER; } +static Token identifier() { + while (isAlpha(peek()) || isDigit(peek())) advance(); + return makeToken(identifierType()); +} static Token number() { while (isDigit(peek())) advance(); diff --git a/clox/scanner.o b/clox/scanner.o index d61a7af..f4b971f 100644 Binary files a/clox/scanner.o and b/clox/scanner.o differ diff --git a/clox/vm.c b/clox/vm.c index 26de285..9c6c687 100644 --- a/clox/vm.c +++ b/clox/vm.c @@ -70,7 +70,19 @@ static InterpretResult run() { #undef READ_BYTE } InterpretResult interpret(const char* source) { - compile(source); - return INTERPRET_OK; + Chunk chunk; + initChunk(&chunk); + if (!compile(source, &chunk)) { + freeChunk(&chunk); + return INTERPRET_COMPILE_ERROR; + } + + vm.chunk = &chunk; + vm.ip = vm.chunk->code; + + InterpretResult result = run(); + + freeChunk(&chunk); + return result; } diff --git a/clox/vm.o b/clox/vm.o index 8dbfc5c..10f26d5 100644 Binary files a/clox/vm.o and b/clox/vm.o differ