diff --git a/CMakeLists.txt b/CMakeLists.txt index dfb8328..e0d695e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,4 +3,4 @@ project(clox C) set(CMAKE_C_STANDARD 11) -add_executable(clox main.c common.h chunk.h chunk.c memory.h debug.h debug.c memory.c value.h value.c vm.c compiler.h compiler.c scanner.h scanner.c) +add_executable(clox main.c common.h chunk.h chunk.c memory.h debug.h debug.c memory.c value.h value.c vm.c compiler.h compiler.c scanner.h scanner.c object.c object.h) diff --git a/compiler.c b/compiler.c index cac6f31..f2860e6 100644 --- a/compiler.c +++ b/compiler.c @@ -134,6 +134,11 @@ static void number() { emitConstant(NUMBER_VAL(value)); } +static void string() { + emitConstant(OBJ_VAL(copyString(parser.previous.start + 1, + parser.previous.length - 2))); +} + static void parsePrecedence(Precedence precedence) { advance(); ParseFn prefixRule = getRule(parser.previous.type)->prefix; @@ -222,7 +227,7 @@ ParseRule rules[] = { [TOKEN_LESS] = {NULL, binary, PREC_COMPARISON}, [TOKEN_LESS_EQUAL] = {NULL, binary, PREC_COMPARISON}, [TOKEN_IDENTIFIER] = {NULL, NULL, PREC_NONE}, - [TOKEN_STRING] = {NULL, NULL, PREC_NONE}, + [TOKEN_STRING] = {string, NULL, PREC_NONE}, [TOKEN_NUMBER] = {number, NULL, PREC_NONE}, [TOKEN_AND] = {NULL, NULL, PREC_NONE}, [TOKEN_CLASS] = {NULL, NULL, PREC_NONE}, diff --git a/compiler.h b/compiler.h index feabdd3..b87641f 100644 --- a/compiler.h +++ b/compiler.h @@ -1,8 +1,9 @@ #ifndef clox_compiler_h #define clox_compiler_h +#include "object.h" #include "vm.h" bool compile(const char* source, Chunk* chunk); -#endif \ No newline at end of file +#endif diff --git a/memory.h b/memory.h index 7ca147b..3b09f40 100644 --- a/memory.h +++ b/memory.h @@ -3,6 +3,9 @@ #include "common.h" +#define ALLOCATE(type, count) \ + (type*)reallocate(NULL, 0, sizeof(type) * (count)) + #define GROW_CAPACITY(capacity) ((capacity) < 8 ? 8 : (capacity) * 2) #define GROW_ARRAY(type, pointer, oldCount, newCount) \ @@ -13,4 +16,4 @@ void* reallocate(void* pointer, size_t oldSize, size_t newSize); -#endif \ No newline at end of file +#endif diff --git a/object.c b/object.c new file mode 100644 index 0000000..9dc0637 --- /dev/null +++ b/object.c @@ -0,0 +1,42 @@ +#include +#include + +#include "memory.h" +#include "object.h" +#include "value.h" +#include "vm.h" + +#define ALLOCATE_OBJ(type, objectType) \ + (type*)allocateObject(sizeof(type), objectType) + +static Obj* allocateObject(size_t size, ObjType type) { + Obj* object = (Obj*)reallocate(NULL, 0, size); + object->type = type; + return object; +} + +static ObjString* allocateString(char* chars, int length) { + ObjString* string = ALLOCATE_OBJ(ObjString, OBJ_STRING); + string->length = length; + string->chars = chars; + return string; +} + +ObjString* copyString(const char* chars, int length) { + char* heapChars = ALLOCATE(char, length+1); + memcpy(heapChars, chars, length); + heapChars[length] = '\0'; + return allocateString(heapChars, length); +} + +ObjString* takeString(char* chars, int length) { + return allocateString(chars, length); +} + +void printObject(Value value) { + switch (OBJ_TYPE(value)) { + case OBJ_STRING: + printf("%s", AS_CSTRING(value)); + break; + } +} diff --git a/object.h b/object.h new file mode 100644 index 0000000..818aadb --- /dev/null +++ b/object.h @@ -0,0 +1,36 @@ +#ifndef clox_object_h +#define clox_object_h + +#include "common.h" +#include "value.h" + +#define OBJ_TYPE(value) (AS_OBJ(value)->type) + +#define IS_STRING(value) isObjType(value, OBJ_STRING) + +#define AS_STRING(value) ((ObjString*)AS_OBJ(value)) +#define AS_CSTRING(value) (((ObjString*)AS_OBJ(value))->chars) + +typedef enum { + OBJ_STRING, +} ObjType; + +struct Obj { + ObjType type; +}; + +struct ObjString { + Obj obj; + int length; + char* chars; +}; + +static inline bool isObjType(Value value, ObjType type) { + return IS_OBJ(value) && AS_OBJ(value)->type == type; +} + +ObjString* takeString(char* chars, int length); +ObjString* copyString(const char* chars, int length); +void printObject(Value value); + +#endif diff --git a/scanner.c b/scanner.c index 3cda260..d731720 100644 --- a/scanner.c +++ b/scanner.c @@ -202,7 +202,7 @@ Token scanToken() { return makeToken(match('=') ? TOKEN_LESS_EQUAL : TOKEN_LESS); case '>': return makeToken(match('=') ? TOKEN_GREATER_EQUAL : TOKEN_GREATER); - case '"': string(); + case '"': return string(); } return errorToken("unexpected character"); diff --git a/value.c b/value.c index b46408a..da7fc88 100644 --- a/value.c +++ b/value.c @@ -1,7 +1,9 @@ #include +#include #include "memory.h" #include "value.h" +#include "object.h" void initValueArray(ValueArray* array) { array->values = NULL; @@ -31,6 +33,7 @@ void printValue(Value value) { break; case VAL_NIL: printf("nil"); break; case VAL_NUMBER: printf("%g", AS_NUMBER(value)); break; + case VAL_OBJ: printObject(value); break; } } @@ -40,6 +43,13 @@ bool valuesEqual(Value a, Value b) { case VAL_BOOL: return AS_BOOL(a) == AS_BOOL(b); case VAL_NIL: return true; case VAL_NUMBER: return AS_NUMBER(a) == AS_NUMBER(b); + case VAL_OBJ: { + ObjString* aString = AS_STRING(a); + ObjString* bString = AS_STRING(b); + return aString->length == bString->length && + memcmp(aString->chars, bString->chars, + aString->length) == 0; + } default: return false; // Unreachable. } -} \ No newline at end of file +} diff --git a/value.h b/value.h index 1665f7c..40e6a37 100644 --- a/value.h +++ b/value.h @@ -1,10 +1,16 @@ #ifndef clox_value_h #define clox_value_h +#include "common.h" + +typedef struct Obj Obj; +typedef struct ObjString ObjString; + typedef enum { VAL_BOOL, VAL_NIL, VAL_NUMBER, + VAL_OBJ, } ValueType; typedef struct { @@ -12,19 +18,23 @@ typedef struct { union { bool boolean; double number; + Obj* obj; } as; } Value; -#define BOOL_VAL(value) ((Value){VAL_BOOL, {.boolean = value}}) -#define NIL_VAL ((Value){VAL_NIL, {.number = 0}}) +#define BOOL_VAL(value) ((Value){VAL_BOOL, {.boolean = value}}) +#define NIL_VAL ((Value){VAL_NIL, {.number = 0}}) #define NUMBER_VAL(value) ((Value){VAL_NUMBER, {.number = value}}) +#define OBJ_VAL(value) ((Value){VAL_OBJ, {.obj = value}}) -#define AS_BOOL(value) ((value).as.boolean) +#define AS_BOOL(value) ((value).as.boolean) #define AS_NUMBER(value) ((value).as.number) +#define AS_OBJ(value) ((value).as.obj) #define IS_BOOL(value) ((value).type == VAL_BOOL) #define IS_NIL(value) ((value).type == VAL_NIL) #define IS_NUMBER(value) ((value).type == VAL_NUMBER) +#define IS_OBJ(value) ((value).type == VAL_OBJ) typedef struct { int capacity; diff --git a/vm.c b/vm.c index aa702f1..ddf686c 100644 --- a/vm.c +++ b/vm.c @@ -1,8 +1,11 @@ #include #include +#include #include "common.h" #include "compiler.h" +#include "object.h" +#include "memory.h" #include "vm.h" #include "debug.h" #include "value.h" @@ -51,6 +54,20 @@ static bool isFalsey(Value value) { return IS_NIL(value) || (IS_BOOL(value) && !AS_BOOL(value)); } +static void concatenate() { + ObjString* b = AS_STRING(pop()); + ObjString* a = AS_STRING(pop()); + + int length = a->length + b->length; + char* chars = ALLOCATE(char, length + 1); + memcpy(chars, a->chars, a->length); + memcpy(chars + a->length, b->chars, b->length); + chars[length] = '\0'; + + ObjString* result = takeString(chars, length); + push(OBJ_VAL(result)); +} + static InterpretResult run() { #define READ_BYTE() (*vm.ip++) #define READ_CONSTANT() (vm.chunk->constants.values[READ_BYTE()]) @@ -90,7 +107,19 @@ static InterpretResult run() { case OP_FALSE: push(BOOL_VAL(false)); break; case OP_GREATER: BINARY_OP(BOOL_VAL, >); break; case OP_LESS: BINARY_OP(BOOL_VAL, <); break; - case OP_ADD: BINARY_OP(NUMBER_VAL, +); break; + case OP_ADD: { + if (IS_STRING(peek(0)) && IS_STRING(peek(1))) { + concatenate(); + } else if (IS_NUMBER(peek(0)) && IS_NUMBER(peek(1))) { + double b = AS_NUMBER(pop()); + double a = AS_NUMBER(pop()); + push(NUMBER_VAL(a + b)); + } else { + runtimeError("Operands must be two numbers or two strings."); + return INTERPRET_RUNTIME_ERROR; + } + break; + } case OP_SUBTRACT: BINARY_OP(NUMBER_VAL, -); break; case OP_MULTIPLY: BINARY_OP(NUMBER_VAL, *); break; case OP_DIVIDE: BINARY_OP(NUMBER_VAL, /); break;