diff --git a/clox/Makefile b/clox/Makefile index ac8ada1..ce89574 100644 --- a/clox/Makefile +++ b/clox/Makefile @@ -1,6 +1,6 @@ CC = gcc CFLAGS = -Wall -Wextra -g -SOURCES = main.c chunk.c memory.c debug.c value.c vm.c compiler.c scanner.c +SOURCES = main.c chunk.c memory.c debug.c value.c vm.c compiler.c scanner.c object.c table.c OBJECTS = $(SOURCES:.c=.o) EXECUTABLE = clox diff --git a/clox/chunk.h b/clox/chunk.h index 34d73c4..3770c32 100644 --- a/clox/chunk.h +++ b/clox/chunk.h @@ -9,11 +9,16 @@ typedef enum { OP_NIL, OP_TRUE, OP_FALSE, + OP_POP, + OP_GET_GLOBAL, + OP_SET_GLOBAL, + OP_DEFINE_GLOBAL, OP_EQUAL, OP_GREATER, - OP_LESS, - OP_RETURN, + OP_LESS, OP_NEGATE, + OP_PRINT, + OP_RETURN, OP_ADD, OP_SUBTRACT, OP_MULTIPLY, diff --git a/clox/clox b/clox/clox index 72d7760..021e15e 100755 Binary files a/clox/clox and b/clox/clox differ diff --git a/clox/compiler.c b/clox/compiler.c index 2ab79d3..7b7da5a 100644 --- a/clox/compiler.c +++ b/clox/compiler.c @@ -30,7 +30,7 @@ typedef enum { PREC_PRIMARY } Precedence; -typedef void (*ParseFn)(); +typedef void (*ParseFn)(bool canAssign); typedef struct { ParseFn prefix; @@ -90,6 +90,16 @@ static void consume(TokenType type, const char* message) { 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); } @@ -126,10 +136,25 @@ static void endCompiler() { #endif } static void expression(); +static void statement(); +static void declaration(); static ParseRule* getRule(TokenType type); static void parsePrecedence(Precedence precedence); -static void binary() { +static uint8_t identifierConstant(Token* name) { + return makeConstant(OBJ_VAL(copyString(name->start,name->length))); +} + +static uint8_t parseVariable(const char* errorMessage) { + consume(TOKEN_IDENTIFIER, errorMessage); + return identifierConstant(&parser.previous); +} + +static void defineVariable(uint8_t global) { + emitBytes(OP_DEFINE_GLOBAL, global); +} + +static void binary(bool canAssign) { TokenType operatorType = parser.previous.type; ParseRule* rule = getRule(operatorType); parsePrecedence((Precedence)(rule->precedence + 1)); @@ -150,7 +175,7 @@ static void binary() { } } -static void literal() { +static void literal(bool canAssign) { switch (parser.previous.type) { case TOKEN_FALSE: emitByte(OP_FALSE); break; case TOKEN_NIL: emitByte(OP_NIL); break; @@ -163,22 +188,107 @@ static void expression() { // What goes here? parsePrecedence(PREC_ASSIGNMENT); } -static void grouping() { + +static void varDeclaration() { + uint8_t global = parseVariable("Expect variable name."); + + if (match(TOKEN_EQUAL)) { + expression(); + } else { + emitByte(OP_NIL); + } + consume(TOKEN_SEMICOLON, + "Expect ';' after variable declaration."); + + defineVariable(global); +} + +static void expressionStatement() { + expression(); + consume(TOKEN_SEMICOLON, "Expect ';' after expression."); + emitByte(OP_POP); +} + +static void printStatement() { + expression(); + consume(TOKEN_SEMICOLON, "Expect ';' after value."); + emitByte(OP_PRINT); +} + +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: + ; // Do nothing. + } + + advance(); + } +} + +static void declaration() { + if (match(TOKEN_VAR)) { + varDeclaration(); + } else { + statement(); + } + + if (parser.panicMode) synchronize(); +} + +static void statement() { + if (match(TOKEN_PRINT)) { + printStatement(); + } + else { + expressionStatement(); + } +} + +static void grouping(bool canAssign) { expression(); consume(TOKEN_RIGHT_PAREN, "Expect ')' after expression."); } -static void number() { +static void number(bool canAssign) { double value = strtod(parser.previous.start, NULL); emitConstant(NUMBER_VAL(value)); } -static void string() { +static void string(bool canAssign) { emitConstant(OBJ_VAL(copyString(parser.previous.start + 1, parser.previous.length - 2))); } -static void unary() { +static void namedVariable(Token name, bool canAssign) { + uint8_t arg = identifierConstant(&name); + + if (canAssign && match(TOKEN_EQUAL)) { + expression(); + emitBytes(OP_SET_GLOBAL, arg); + } else { + emitBytes(OP_GET_GLOBAL, arg); + } +} + +static void variable(bool canAssign) { + namedVariable(parser.previous, canAssign); +} + +static void unary(bool canAssign) { TokenType operatorType = parser.previous.type; // Compile the operand. @@ -201,12 +311,16 @@ static void parsePrecedence(Precedence precedence) { return; } - prefixRule(); + bool canAssign = precedence <= PREC_ASSIGNMENT; + prefixRule(canAssign); while (precedence <= getRule(parser.current.type)->precedence) { advance(); ParseFn infixRule = getRule(parser.previous.type)->infix; - infixRule(); + infixRule(canAssign); + } + if (canAssign && match(TOKEN_EQUAL)) { + error("Invalid assignment target."); } } @@ -230,7 +344,7 @@ ParseRule rules[] = { [TOKEN_GREATER_EQUAL] = {NULL, binary, PREC_COMPARISON}, [TOKEN_LESS] = {NULL, binary, PREC_COMPARISON}, [TOKEN_LESS_EQUAL] = {NULL, binary, PREC_COMPARISON}, - [TOKEN_IDENTIFIER] = {NULL, NULL, PREC_NONE}, + [TOKEN_IDENTIFIER] = {variable, NULL, PREC_NONE}, [TOKEN_STRING] = {string, NULL, PREC_NONE}, [TOKEN_NUMBER] = {number, NULL, PREC_NONE}, [TOKEN_AND] = {NULL, NULL, PREC_NONE}, @@ -263,8 +377,11 @@ bool compile(const char* source, Chunk* chunk) { parser.hadError = false; parser.panicMode = false; advance(); - expression(); - consume(TOKEN_EOF, "Expect end of expression."); + while (!match(TOKEN_EOF)) { + declaration(); + } + // expression(); + // consume(TOKEN_EOF, "Expect end of expression."); endCompiler(); return !parser.hadError; } diff --git a/clox/compiler.o b/clox/compiler.o index 541d9fb..ed1032b 100644 Binary files a/clox/compiler.o and b/clox/compiler.o differ diff --git a/clox/debug.c b/clox/debug.c index 4175c21..febedc2 100644 --- a/clox/debug.c +++ b/clox/debug.c @@ -42,6 +42,14 @@ int disassembleInstruction(Chunk* chunk, int offset) { return simpleInstruction("OP_TRUE", offset); case OP_FALSE: return simpleInstruction("OP_FALSE", offset); + case OP_POP: + return simpleInstruction("OP_POP", offset); + case OP_GET_GLOBAL: + return constantInstruction("OP_GET_GLOBAL", chunk, offset); + case OP_DEFINE_GLOBAL: + return constantInstruction("OP_DEFINE_GLOBAL", chunk,offset); + case OP_SET_GLOBAL: + return constantInstruction("OP_SET_GLOBAL", chunk, offset); case OP_EQUAL: return simpleInstruction("OP_EQUAL", offset); case OP_GREATER: @@ -60,6 +68,8 @@ int disassembleInstruction(Chunk* chunk, int offset) { return simpleInstruction("OP_NEGATE", offset); case OP_NOT: return simpleInstruction("OP_NOT", offset); + case OP_PRINT: + return simpleInstruction("OP_PRINT", offset); case OP_RETURN: return simpleInstruction("OP_RETURN", offset); default: diff --git a/clox/debug.o b/clox/debug.o index e37916e..5d938b5 100644 Binary files a/clox/debug.o and b/clox/debug.o differ diff --git a/clox/memory.o b/clox/memory.o index 94c0355..1ed7c15 100644 Binary files a/clox/memory.o and b/clox/memory.o differ diff --git a/clox/tests/chapter21_globals.lox b/clox/tests/chapter21_globals.lox new file mode 100644 index 0000000..87eed5c --- /dev/null +++ b/clox/tests/chapter21_globals.lox @@ -0,0 +1,5 @@ +var breakfast = "beignets"; +var beverage = "cafe au lait"; +breakfast = "beignets with " + beverage; + +print breakfast; \ No newline at end of file diff --git a/clox/value.h b/clox/value.h index 6a9b6bb..a5be2c6 100644 --- a/clox/value.h +++ b/clox/value.h @@ -28,6 +28,7 @@ typedef struct { #define IS_NUMBER(value) ((value).type == VAL_NUMBER) #define IS_OBJ(value) ((value).type == VAL_OBJ) +#define AS_OBJ(value) ((value).as.obj) #define AS_BOOL(value) ((value).as.boolean) #define AS_NUMBER(value) ((value).as.number) #define BOOL_VAL(value) ((Value){VAL_BOOL, {.boolean = value}}) diff --git a/clox/vm.c b/clox/vm.c index 6420f92..d96afaf 100644 --- a/clox/vm.c +++ b/clox/vm.c @@ -7,6 +7,7 @@ #include "object.h" #include "memory.h" #include "vm.h" +#include "table.h" VM vm; static void resetStack() { vm.stackTop = vm.stack; @@ -28,10 +29,12 @@ static void runtimeError(const char* format, ...) { void initVM() { resetStack(); vm.objects = NULL; + initTable(&vm.globals); initTable(&vm.strings); } void freeVM() { + freeTable(&vm.globals); freeTable(&vm.strings); freeObjects(); } @@ -69,6 +72,7 @@ static void concatenate() { static InterpretResult run() { #define READ_BYTE() (*vm.ip++) #define READ_CONSTANT() (vm.chunk->constants.values[READ_BYTE()]) +#define READ_STRING() AS_STRING(READ_CONSTANT()) #define BINARY_OP(valueType, op) \ do { \ if (!IS_NUMBER(peek(0)) || !IS_NUMBER(peek(1))) { \ @@ -103,6 +107,15 @@ static InterpretResult run() { case OP_NIL: push(NIL_VAL); break; case OP_TRUE: push(BOOL_VAL(true)); break; case OP_FALSE: push(BOOL_VAL(false)); break; + case OP_SET_GLOBAL: { + ObjString* name = READ_STRING(); + if (tableSet(&vm.globals, name, peek(0))) { + tableDelete(&vm.globals, name); + runtimeError("Undefined variable '%s'.", name->chars); + return INTERPRET_RUNTIME_ERROR; + } + break; + } case OP_EQUAL: { Value b = pop(); Value a = pop(); @@ -131,6 +144,23 @@ static InterpretResult run() { case OP_NOT: push(BOOL_VAL(isFalsey(pop()))); break; + case OP_POP: pop(); break; + case OP_GET_GLOBAL: { + ObjString* name = READ_STRING(); + Value value; + if (!tableGet(&vm.globals, name, &value)) { + runtimeError("Undefined variable '%s'.", name->chars); + return INTERPRET_RUNTIME_ERROR; + } + push(value); + break; + } + case OP_DEFINE_GLOBAL: { + ObjString* name = READ_STRING(); + tableSet(&vm.globals, name, peek(0)); + pop(); + break; + } case OP_NEGATE: if (!IS_NUMBER(peek(0))) { runtimeError("Operand must be a number."); @@ -138,14 +168,21 @@ static InterpretResult run() { } push(NUMBER_VAL(-AS_NUMBER(pop()))); break; - case OP_RETURN: { + case OP_PRINT: { printValue(pop()); printf("\n"); + break; + } + case OP_RETURN: { + // printValue(pop()); + // printf("\n"); return INTERPRET_OK; } + } } #undef READ_CONSTANT +#undef READ_STRING #undef BINARY_OP #undef READ_BYTE } diff --git a/clox/vm.h b/clox/vm.h index 6cd3374..87d8ce5 100644 --- a/clox/vm.h +++ b/clox/vm.h @@ -12,6 +12,7 @@ typedef struct { uint8_t* ip; Value stack[STACK_MAX]; Value* stackTop; + Table globals; Table strings; Obj* objects; } VM; diff --git a/clox/vm.o b/clox/vm.o index 0873f42..7d22bfe 100644 Binary files a/clox/vm.o and b/clox/vm.o differ