From d8b4f9b2ea39a124ba85b23b405b9bb0d9728d67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Luis=20Monta=C3=B1es=20Ojados?= Date: Thu, 19 Feb 2026 04:24:44 +0100 Subject: [PATCH] Add class support with constructors, fields, and methods --- projects/classes.j | 11 ++ projects/custom_fn.j | 10 +- src/backend/bytecode/compiler.h | 170 ++++++++++++++++++++++++++++++- src/backend/bytecode/opcodes.h | 62 +++++++----- src/backend/bytecode/vm.h | 174 +++++++++++++++++++++++++++++++- src/frontend/lexer.h | 36 ++++--- src/frontend/parser.h | 161 ++++++++++++++++++++++++++--- src/memory/gc.h | 113 ++++++++++++--------- src/objects/object.h | 40 +++++++- 9 files changed, 660 insertions(+), 117 deletions(-) create mode 100644 projects/classes.j diff --git a/projects/classes.j b/projects/classes.j new file mode 100644 index 0000000..44b0446 --- /dev/null +++ b/projects/classes.j @@ -0,0 +1,11 @@ +class Dog: + fn init(self, name): + self.name = name + + fn bark(self): + println("guau!") + +d = Dog("ahi te va") +x = d.bark() +println("Hola ", d.name) +debugHeap() \ No newline at end of file diff --git a/projects/custom_fn.j b/projects/custom_fn.j index 74c80dc..c0d5982 100644 --- a/projects/custom_fn.j +++ b/projects/custom_fn.j @@ -1,4 +1,8 @@ -fn greet(name): - println("Hola, " + name) +x = "Hello world!" -greet("mundo!") \ No newline at end of file +fn suma(x, y): + fn pow(z): + return z * z + return x + pow(y) + +println(suma(2, 2)) \ No newline at end of file diff --git a/src/backend/bytecode/compiler.h b/src/backend/bytecode/compiler.h index e1ee711..ce831d0 100644 --- a/src/backend/bytecode/compiler.h +++ b/src/backend/bytecode/compiler.h @@ -5,6 +5,18 @@ #include "opcodes.h" #include +typedef struct { + char *method_name; + int entry_point; + int param_count; + char **param_names; +} MethodEntry; + +typedef struct { + char *name; + MethodEntry methods[16]; + int method_count; +} ClassEntry; typedef struct { char *name; @@ -22,6 +34,8 @@ typedef struct { int name_count; FunctionEntry functions[64]; int func_count; + ClassEntry classes[16]; + int class_count; } Chunk; int emit(Chunk *chunk, Instruction instr) { @@ -86,6 +100,26 @@ int compile_node(Chunk *chunk, ASTNode *node) { compile_node(chunk, node->data.call.args[i]); } + // Verificar si es constructor de una clase + for (int i = 0; i < chunk->class_count; i++) { + if (strcmp(chunk->classes[i].name, node->data.call.name) == 0) { + // Buscar init + for (int m = 0; m < chunk->classes[i].method_count; m++) { + if (strcmp(chunk->classes[i].methods[m].method_name, "init") == 0) { + int expected = + chunk->classes[i].methods[m].param_count - 1; // -1 por self + if (node->data.call.arg_count != expected) { + printf("error: %s() espera %d args, pero recibio %d\n", + node->data.call.name, expected, node->data.call.arg_count); + exit(1); + } + break; + } + } + break; + } + } + // Registrar el nombre de la funcion Instruction instr = make_instruction(OP_CALL); instr.operand.call.arg_count = node->data.call.arg_count; @@ -170,6 +204,13 @@ int compile_node(Chunk *chunk, ASTNode *node) { break; } + case NODE_RETURN: { + if (node->data.ret.value) { + compile_node(chunk, node->data.ret.value); + } + emit(chunk, make_instruction(OP_RETURN)); + return 0; + } case NODE_FN_DEF: { // emitir jmp para ignorar la funcion por defecto Instruction jump = make_instruction(OP_JUMP); @@ -183,14 +224,14 @@ int compile_node(Chunk *chunk, ASTNode *node) { fn->entry_point = entry; fn->param_count = node->data.fn_def.param_count; fn->param_names = node->data.fn_def.params; - + // emitir store_var para cada parametro (orden inverso al stack) for (int i = node->data.fn_def.param_count - 1; i >= 0; i--) { - Instruction store = make_instruction(OP_STORE_VAR); - store.operand.var_index = add_name(chunk, node->data.fn_def.params[i]); - emit(chunk, store); + Instruction store = make_instruction(OP_STORE_VAR); + store.operand.var_index = add_name(chunk, node->data.fn_def.params[i]); + emit(chunk, store); } - + // compilar el cuerpo compile_node(chunk, node->data.fn_def.body); @@ -201,6 +242,80 @@ int compile_node(Chunk *chunk, ASTNode *node) { chunk->code[jump_idx].operand.jump_target = chunk->code_count; break; } + + case NODE_CLASS_DEF: { + // Registrar ClassEntry + ClassEntry *cls = &chunk->classes[chunk->class_count++]; + cls->name = node->data.class_def.name; + cls->method_count = 0; + + // Pre-registrar self en la tabla de nombres + add_name(chunk, "self"); + + int totalMethods = node->data.class_def.method_count; + for (int i = 0; i < totalMethods; i++) { + ASTNode *method = node->data.class_def.methods[i]; + + // jump over + Instruction jump = make_instruction(OP_JUMP); + jump.operand.jump_target = -1; + int jump_idx = emit(chunk, jump); + + // Registrar method entry + int entry = chunk->code_count; + MethodEntry *me = &cls->methods[cls->method_count++]; + me->method_name = method->data.fn_def.name; + me->entry_point = entry; + me->param_count = method->data.fn_def.param_count; + me->param_names = method->data.fn_def.params; + + // store_var para cada parametro (orden inverso) + for (int p = method->data.fn_def.param_count - 1; p >= 0; p--) { + Instruction store = make_instruction(OP_STORE_VAR); + store.operand.var_index = + add_name(chunk, method->data.fn_def.params[p]); + emit(chunk, store); + } + + // Compilar body + compile_node(chunk, method->data.fn_def.body); + + // return implicito + emit(chunk, make_instruction(OP_RETURN)); + + // Backpatch + chunk->code[jump_idx].operand.jump_target = chunk->code_count; + } + break; + } + case NODE_DOT_ACCESS: { + compile_node(chunk, node->data.dot_access.object); + Instruction instr = make_instruction(OP_GET_FIELD); + instr.operand.var_index = add_name(chunk, node->data.dot_access.field); + emit(chunk, instr); + break; + } + case NODE_DOT_ASSIGN: { + compile_node(chunk, node->data.dot_assign.value); // push valor + compile_node(chunk, node->data.dot_assign.object); // push instancia + Instruction instr = make_instruction(OP_SET_FIELD); + instr.operand.var_index = add_name(chunk, node->data.dot_assign.field); + emit(chunk, instr); + break; + } + case NODE_METHOD_CALL: { + compile_node(chunk, node->data.method_call.object); // push instancia + for (int i = 0; i < node->data.method_call.arg_count; i++) { + compile_node(chunk, node->data.method_call.args[i]); + } + Instruction instr = make_instruction(OP_CALL_METHOD); + instr.operand.call.name_index = + add_name(chunk, node->data.method_call.method); + instr.operand.call.arg_count = node->data.method_call.arg_count; + emit(chunk, instr); + break; + } + default: break; } @@ -298,12 +413,57 @@ void print_chunk(Chunk *chunk) { case OP_HALT: printf("HALT"); break; + case OP_GET_FIELD: + printf("GET_FIELD [%d] %s", instr.operand.var_index, + chunk->names[instr.operand.var_index]); + break; + case OP_SET_FIELD: + printf("SET_FIELD [%d] %s", instr.operand.var_index, + chunk->names[instr.operand.var_index]); + break; + case OP_CALL_METHOD: + printf("CALL_METHOD %s(%d args)", + chunk->names[instr.operand.call.name_index], + instr.operand.call.arg_count); + break; + default: printf("UNKNOWN op=%d", instr.op); break; } printf("\n"); } + printf("=== User Functions ===\n"); + for (int i = 0; i < chunk->func_count; i++) { + FunctionEntry *fn = &chunk->functions[i]; + printf("[%.4d] %s(", fn->entry_point, fn->name); + + for (int p = 0; p < fn->param_count; p++) { + printf("%s", fn->param_names[p]); + + if (p < fn->param_count - 1) { + printf(", "); + } + } + printf(")\n"); + } + + printf("=== Classes ===\n"); + for (int i = 0; i < chunk->class_count; i++) { + ClassEntry *cls = &chunk->classes[i]; + printf("class %s (%d methods)\n", cls->name, cls->method_count); + for (int m = 0; m < cls->method_count; m++) { + MethodEntry *me = &cls->methods[m]; + printf(" [%.4d] %s(", me->entry_point, me->method_name); + for (int p = 0; p < me->param_count; p++) { + printf("%s", me->param_names[p]); + if (p < me->param_count - 1) + printf(", "); + } + printf(")\n"); + } + } + printf("=== End ===\n"); } diff --git a/src/backend/bytecode/opcodes.h b/src/backend/bytecode/opcodes.h index b2b62ff..0b4808f 100644 --- a/src/backend/bytecode/opcodes.h +++ b/src/backend/bytecode/opcodes.h @@ -2,36 +2,42 @@ #define JLANG_OPCODES_H typedef enum { - OP_CONST_INT, // push entero inmediato - OP_CONST_STRING, // push string desde pool de constantes (alloc en heap) - OP_POP, // descarta top del stack - OP_ADD, OP_SUB, OP_MUL, OP_DIV, // aritmetica - OP_NEG, // negacion unaria - OP_CMP_LT, OP_CMP_GT, // comparacion -> push 0 o 1 - OP_LOAD_VAR, // push variable por indice - OP_STORE_VAR, // pop -> guardar en variable por indice - OP_JUMP, // salto incondicional - OP_JUMP_IF_ZERO, // pop -> si false, saltar - OP_CALL, // llamar built-in por indice de nombre - OP_RETURN, // retornar de funcion (pop call frame) - OP_NOP, OP_HALT, + OP_CONST_INT, // push entero inmediato + OP_CONST_STRING, // push string desde pool de constantes (alloc en heap) + OP_POP, // descarta top del stack + OP_ADD, + OP_SUB, + OP_MUL, + OP_DIV, // aritmetica + OP_NEG, // negacion unaria + OP_CMP_LT, + OP_CMP_GT, // comparacion -> push 0 o 1 + OP_LOAD_VAR, // push variable por indice + OP_STORE_VAR, // pop -> guardar en variable por indice + OP_JUMP, // salto incondicional + OP_JUMP_IF_ZERO, // pop -> si false, saltar + OP_CALL, // llamar built-in por indice de nombre + OP_RETURN, // retornar de funcion (pop call frame) + OP_NOP, + OP_HALT, + OP_GET_FIELD, // TOS=instance, operand=name_idx → push field value + OP_SET_FIELD, // TOS=instance, TOS-1=value, operand=name_idx → set field + OP_CALL_METHOD, // TOS-N-1=instance + N args, operand={name_idx, arg_count} + } OpCode; -typedef struct -{ - OpCode op; - union - { - int int_val; // OP_CONST_INT - int str_index; // OP_CONST_STRING: indice a pool de constantes - int var_index; // OP_LOAD_VAR, OP_STORE_VAR - int jump_target; // OP_JUMP, OP_JUMP_IF_ZERO - struct - { - int name_index; - int arg_count; - } call; // OP_CALL - } operand; +typedef struct { + OpCode op; + union { + int int_val; // OP_CONST_INT + int str_index; // OP_CONST_STRING: indice a pool de constantes + int var_index; // OP_LOAD_VAR, OP_STORE_VAR + int jump_target; // OP_JUMP, OP_JUMP_IF_ZERO + struct { + int name_index; + int arg_count; + } call; // OP_CALL + } operand; } Instruction; #endif \ No newline at end of file diff --git a/src/backend/bytecode/vm.h b/src/backend/bytecode/vm.h index 5726236..78e626b 100644 --- a/src/backend/bytecode/vm.h +++ b/src/backend/bytecode/vm.h @@ -10,6 +10,8 @@ typedef struct { int saved_sp; // base del stack Value saved_vars[256]; // variables del caller (snapshot) int saved_var_set[256]; + int is_constructor; + Value constructor_instance; } CallFrame; typedef struct { @@ -115,6 +117,7 @@ void run_vm(VM *vm) { CallFrame *frame = &vm->frames[vm->frame_count++]; frame->return_ip = vm->ip + 1; // volver a la siguiente instruccion frame->saved_sp = vm->sp - fn->param_count; + frame->is_constructor = 0; memcpy(frame->saved_vars, vm->vars, sizeof(vm->vars)); memcpy(frame->saved_var_set, vm->var_set, sizeof(vm->var_set)); @@ -123,6 +126,63 @@ void run_vm(VM *vm) { continue; // no hacer ip++ } + // Buscar en classes[] + ClassEntry *cls = NULL; + int class_idx = -1; + for (int i = 0; i < vm->chunk->class_count; i++) { + if (strcmp(vm->chunk->classes[i].name, name) == 0) { + cls = &vm->chunk->classes[i]; + class_idx = i; + break; + } + } + + if (cls != NULL) { + // alloc instancia + size_t instOffset = + obj_new_instance(vm->allocator, class_idx, 8, sizeof(Value)); + Value instVal = {0}; + instVal.type = VAL_OBJ; + instVal.as.heap_offset = instOffset; + + // buscar init + MethodEntry *init = NULL; + for (int m = 0; m < cls->method_count; m++) { + if (strcmp(cls->methods[m].method_name, "init") == 0) { + init = &cls->methods[m]; + break; + } + } + + if (init != NULL) { + // insertar instancia bajo de los args para self + int nArgs = instr.operand.call.arg_count; + for (int a = vm->sp - 1; a >= vm->sp - nArgs; a--) { + vm->stack[a + 1] = vm->stack[a]; + } + vm->stack[vm->sp - nArgs] = instVal; + vm->sp++; + + // Save frame + CallFrame *frame = &vm->frames[vm->frame_count++]; + frame->return_ip = vm->ip + 1; + frame->saved_sp = vm->sp - nArgs - 1; + frame->is_constructor = 1; + frame->constructor_instance = instVal; + memcpy(frame->saved_vars, vm->vars, sizeof(vm->vars)); + ; + memcpy(frame->saved_var_set, vm->var_set, sizeof(vm->var_set)); + + vm->ip = init->entry_point; + continue; + } else { + // No hay init, descartar args y devolver instancia + vm->sp -= instr.operand.call.arg_count; + vm->stack[vm->sp++] = instVal; + } + break; + } + if (strcmp(name, "print") == 0 || strcmp(name, "println") == 0) { int nParams = instr.operand.call.arg_count; @@ -178,7 +238,7 @@ void run_vm(VM *vm) { // Captrurar valor de retorno si hay alguno en el stack Value return_val = {0}; int has_return = 0; - if (vm->sp > vm->frames[vm->frame_count-1].saved_sp) { + if (vm->sp > vm->frames[vm->frame_count - 1].saved_sp) { return_val = vm->stack[--vm->sp]; has_return = 1; } @@ -191,8 +251,13 @@ void run_vm(VM *vm) { memcpy(vm->var_set, frame->saved_var_set, sizeof(vm->var_set)); // Push return value - if (has_return) { + if (frame->is_constructor) { + vm->stack[vm->sp++] = frame->constructor_instance; + } else if (has_return) { vm->stack[vm->sp++] = return_val; + } else { + Value nil = {0}; + vm->stack[vm->sp++] = nil; } continue; @@ -322,6 +387,19 @@ void run_vm(VM *vm) { for (int i = 0; i < 256; i++) { if (vm->var_set[i] && vm->vars[i].type == VAL_OBJ) { roots[root_count++] = vm->vars[i].as.heap_offset; + + // si es instancia agregar fields + Object *obj = + JLANG_RESOLVE(vm->allocator, vm->vars[i].as.heap_offset); + if (obj->type == OBJ_INSTANCE) { + Value *values = (Value *)JLANG_RESOLVE( + vm->allocator, obj->data.instance_val.field_values); + for (int f = 0; f < obj->data.instance_val.field_count; f++) { + if (values[f].type == VAL_OBJ) { + roots[root_count++] = values[f].as.heap_offset; + } + } + } } } @@ -336,6 +414,98 @@ void run_vm(VM *vm) { break; } + case OP_GET_FIELD: { + Value instance = vm->stack[--vm->sp]; + Object *obj = JLANG_RESOLVE(vm->allocator, instance.as.heap_offset); + int name_idx = instr.operand.var_index; + + int *names = (int *)JLANG_RESOLVE(vm->allocator, + obj->data.instance_val.field_names); + Value *values = (Value *)JLANG_RESOLVE( + vm->allocator, obj->data.instance_val.field_values); + + int found = 0; + for (int i = 0; i < obj->data.instance_val.field_count; i++) { + if (names[i] == name_idx) { + vm->stack[vm->sp++] = values[i]; + found = 1; + break; + } + } + + if (!found) { + printf("error: field '%s' not found\n", vm->chunk->names[name_idx]); + return; + } + break; + } + + case OP_SET_FIELD: { + Value instance = vm->stack[--vm->sp]; + Value value = vm->stack[--vm->sp]; + int name_idx = instr.operand.var_index; + + Object *obj = JLANG_RESOLVE(vm->allocator, instance.as.heap_offset); + int *names = (int *)JLANG_RESOLVE(vm->allocator, + obj->data.instance_val.field_names); + Value *values = (Value *)JLANG_RESOLVE( + vm->allocator, obj->data.instance_val.field_values); + + int found = 0; + for (int i = 0; i < obj->data.instance_val.field_count; i++) { + if (names[i] == name_idx) { + values[i] = value; + found = 1; + break; + } + } + + if (!found) { + // Agregar campo nuevo + int idx = obj->data.instance_val.field_count++; + names[idx] = name_idx; + values[idx] = value; + } + break; + } + + case OP_CALL_METHOD: { + int method_name_idx = instr.operand.call.name_index; + int arg_count = instr.operand.call.arg_count; + char *method_name = vm->chunk->names[method_name_idx]; + + // la instancia está bajo de los args + Value instance = vm->stack[vm->sp - arg_count - 1]; + Object *obj = JLANG_RESOLVE(vm->allocator, instance.as.heap_offset); + ClassEntry *cls = &vm->chunk->classes[obj->data.instance_val.class_index]; + + // Buscar metodo + MethodEntry *method = NULL; + for (int i = 0; i < cls->method_count; i++) { + if (strcmp(cls->methods[i].method_name, method_name) == 0) { + method = &cls->methods[i]; + break; + } + } + + if (method == NULL) { + printf("error: method '%s' not found in class '%s'\n", method_name, + cls->name); + return; + } + + // Save frame + CallFrame *frame = &vm->frames[vm->frame_count++]; + frame->return_ip = vm->ip + 1; + frame->saved_sp = vm->sp - arg_count - 1; + frame->is_constructor = 0; + memcpy(frame->saved_vars, vm->vars, sizeof(vm->vars)); + memcpy(frame->saved_var_set, vm->var_set, sizeof(vm->var_set)); + + vm->ip = method->entry_point; + continue; + } + default: break; } diff --git a/src/frontend/lexer.h b/src/frontend/lexer.h index d1300ad..1ba9e4c 100644 --- a/src/frontend/lexer.h +++ b/src/frontend/lexer.h @@ -14,11 +14,12 @@ typedef enum { TOK_STRING, // "hola" // Identificadores y keywords - TOK_ID, // x, foo, mi_var - TOK_IF, // if - TOK_WHILE, // while - TOK_FN, // fn - TOK_RETURN, // return + TOK_ID, // x, foo, mi_var + TOK_IF, // if + TOK_WHILE, // while + TOK_FN, // fn + TOK_RETURN, // return + TOK_CLASS, // class // Operadores TOK_ASSIGN, // = @@ -36,6 +37,7 @@ typedef enum { TOK_RPAREN, // ) TOK_COLON, // : TOK_COMMA, // , + TOK_DOT, // . TOK_NEWLINE, // \n (significativo, como en Python) TOK_INDENT, // aumento de indentacion TOK_DEDENT, // reduccion de indentacion @@ -89,12 +91,17 @@ Token *tokenize(const char *source, int *token_count) { pos++; } int new_level = spaces / 4; // 4 espacios = 1 nivel - if (new_level > indent_level) { - tokens[count++] = make_token(TOK_INDENT, "INDENT"); - } else if (new_level < indent_level) { - tokens[count++] = make_token(TOK_DEDENT, "DEDENT"); + if (source[pos] != '\n' && source[pos] != '\0') { + if (new_level > indent_level) { + for (int l = indent_level; l < new_level; l++) + tokens[count++] = make_token(TOK_INDENT, "INDENT"); + } else if (new_level < indent_level) { + for (int l = indent_level; l > new_level; l--) + tokens[count++] = make_token(TOK_DEDENT, "DEDENT"); + } + indent_level = new_level; } - indent_level = new_level; + } else if (c == '+') { tokens[count++] = make_token(TOK_PLUS, "+"); pos++; @@ -122,6 +129,9 @@ Token *tokenize(const char *source, int *token_count) { } else if (c == ',') { tokens[count++] = make_token(TOK_COMMA, ","); pos++; + } else if (c == '.') { + tokens[count++] = make_token(TOK_DOT, "."); + pos++; } else if (c == '(') { tokens[count++] = make_token(TOK_LPAREN, "("); pos++; @@ -142,10 +152,10 @@ Token *tokenize(const char *source, int *token_count) { while (source[pos] >= '0' && source[pos] <= '9') pos++; tokens[count++] = make_token(TOK_INT, substr(source, start, pos)); - } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + } else if ((c >= 'a' && c <= 'z') || (c == '_') || (c >= 'A' && c <= 'Z')) { // Leer todos los caracteres consecutivos int start = pos; - while (isalnum(source[pos])) + while (isalnum(source[pos]) || source[pos] == '_') pos++; char *word = substr(source, start, pos); // tokens[count++] = make_token(TOK_ID, word); @@ -159,6 +169,8 @@ Token *tokenize(const char *source, int *token_count) { tokens[count++] = make_token(TOK_FN, word); } else if (strcmp(word, "return") == 0) { tokens[count++] = make_token(TOK_RETURN, word); + } else if (strcmp(word, "class") == 0) { + tokens[count++] = make_token(TOK_CLASS, word); } else { tokens[count++] = make_token(TOK_ID, word); } diff --git a/src/frontend/parser.h b/src/frontend/parser.h index cf200aa..cada036 100644 --- a/src/frontend/parser.h +++ b/src/frontend/parser.h @@ -18,7 +18,11 @@ typedef enum { NODE_BLOCK, // secuencia de statements NODE_CALL, NODE_FN_DEF, // definicion de funcion - NODE_RETURN, // return expr + NODE_RETURN, // return expr + NODE_CLASS_DEF, // definicion de clase + NODE_DOT_ACCESS, + NODE_DOT_ASSIGN, + NODE_METHOD_CALL, } NodeType; typedef struct ASTNode { @@ -63,7 +67,27 @@ typedef struct ASTNode { } fn_def; // NODE_FN_DEF struct { struct ASTNode *value; // expresion de retorno - } ret; // NODE_RETURN + } ret; // NODE_RETURN + struct { + char *name; + struct ASTNode **methods; + int method_count; + } class_def; + struct { + struct ASTNode *object; + char *field; + } dot_access; + struct { + struct ASTNode *object; + char *field; + struct ASTNode *value; + } dot_assign; + struct { + struct ASTNode *object; + char *method; + struct ASTNode **args; + int arg_count; + } method_call; } data; } ASTNode; @@ -91,6 +115,7 @@ ASTNode *parse_factor(Token *tokens) { pos++; return node; } else if (tokens[pos].type == TOK_ID) { + ASTNode *node; if (tokens[pos + 1].type == TOK_LPAREN) { // Function call char *name = tokens[pos].value; @@ -110,15 +135,50 @@ ASTNode *parse_factor(Token *tokens) { } } pos++; // consumir ")" - ASTNode *node = make_node(NODE_CALL); + node = make_node(NODE_CALL); node->data.call.name = name; node->data.call.args = args; node->data.call.arg_count = arg_count; - return node; + } else { + node = make_node(NODE_VAR); + node->data.string_val = tokens[pos].value; + pos++; } - ASTNode *node = make_node(NODE_VAR); - node->data.string_val = tokens[pos].value; - pos++; + + // Loop post-fix para dot access + while (tokens[pos].type == TOK_DOT) { + pos++; // consumir . + char *field = tokens[pos].value; + pos++; // consumir field name + + if (tokens[pos].type == TOK_LPAREN) { + // obj.method(args...) + pos++; // consumir '(' + ASTNode **args = (ASTNode **)malloc(sizeof(ASTNode *) * 16); + int arg_count = 0; + if (tokens[pos].type != TOK_RPAREN) { + args[arg_count++] = parse_expr(tokens); + while (tokens[pos].type == TOK_COMMA) { + pos++; + args[arg_count++] = parse_expr(tokens); + } + } + pos++; // consumir ')' + ASTNode *mc = make_node(NODE_METHOD_CALL); + mc->data.method_call.object = node; + mc->data.method_call.method = field; + mc->data.method_call.args = args; + mc->data.method_call.arg_count = arg_count; + node = mc; + } else { + // obj.field + ASTNode *da = make_node(NODE_DOT_ACCESS); + da->data.dot_access.object = node; + da->data.dot_access.field = field; + node = da; + } + } + return node; } else if (tokens[pos].type == TOK_LPAREN) { @@ -179,6 +239,24 @@ ASTNode *parse_term(Token *tokens) { ASTNode *parse_statement(Token *tokens) { if (tokens[pos].type == TOK_ID) { + if (tokens[pos + 1].type == TOK_DOT) { + ASTNode *expr = parse_expr(tokens); + + if (tokens[pos].type == TOK_ASSIGN) { + // dot_assign: self.name = expr + pos++; // consumir '=' + ASTNode *value = parse_expr(tokens); + ASTNode *node = make_node(NODE_DOT_ASSIGN); + node->data.dot_assign.object = expr->data.dot_access.object; + node->data.dot_assign.field = expr->data.dot_access.field; + node->data.dot_assign.value = value; + return node; + } + + // si no hay '=', es un method call como (d.speak()) + return expr; + } + if (tokens[pos + 1].type == TOK_LPAREN) { // Es una funcion char *name = tokens[pos].value; @@ -246,7 +324,7 @@ ASTNode *parse_statement(Token *tokens) { while (tokens[pos].type != TOK_DEDENT) { body->data.block.stmts[body->data.block.count++] = parse_statement(tokens); - if (tokens[pos].type == TOK_NEWLINE) { + while (tokens[pos].type == TOK_NEWLINE) { pos++; } } @@ -272,7 +350,7 @@ ASTNode *parse_statement(Token *tokens) { while (tokens[pos].type != TOK_DEDENT) { body->data.block.stmts[body->data.block.count++] = parse_statement(tokens); - if (tokens[pos].type == TOK_NEWLINE) { + while (tokens[pos].type == TOK_NEWLINE) { pos++; } } @@ -284,9 +362,34 @@ ASTNode *parse_statement(Token *tokens) { return node; } + if (tokens[pos].type == TOK_CLASS) { + pos++; // consumir 'class' + char *name = tokens[pos].value; + pos++; // consumir nombre + pos++; // consumir : + pos++; // consumir NEWLINE + pos++; // consumir INDENT + + ASTNode **methods = (ASTNode **)malloc(sizeof(ASTNode *) * 16); + int method_count = 0; + while (tokens[pos].type != TOK_DEDENT) { + methods[method_count++] = parse_statement(tokens); + while (tokens[pos].type == TOK_NEWLINE) { + pos++; + } + } + pos++; // consumir DEDENT + + ASTNode *node = make_node(NODE_CLASS_DEF); + node->data.class_def.name = name; + node->data.class_def.methods = methods; + node->data.class_def.method_count = method_count; + return node; + } + if (tokens[pos].type == TOK_FN) { pos++; // consumir "fn" - char* name = tokens[pos].value; + char *name = tokens[pos].value; pos++; // consumir name pos++; // consumir "(" @@ -314,13 +417,13 @@ ASTNode *parse_statement(Token *tokens) { while (tokens[pos].type != TOK_DEDENT) { body->data.block.stmts[body->data.block.count++] = parse_statement(tokens); - if (tokens[pos].type == TOK_NEWLINE) { + while (tokens[pos].type == TOK_NEWLINE) { pos++; } } pos++; // Consumir DEDENT - ASTNode*node = make_node(NODE_FN_DEF); + ASTNode *node = make_node(NODE_FN_DEF); node->data.fn_def.name = name; node->data.fn_def.params = params; node->data.fn_def.param_count = param_count; @@ -334,7 +437,7 @@ ASTNode *parse_statement(Token *tokens) { node->data.ret.value = parse_expr(tokens); return node; } - + printf("ERROR: statement inesperado\n"); exit(1); } @@ -345,7 +448,7 @@ ASTNode *parse(Token *tokens, int token_count) { block->data.block.count = 0; while (pos < token_count) { - if (tokens[pos].type == TOK_NEWLINE) { + while (tokens[pos].type == TOK_NEWLINE) { pos++; // Saltar newlines sueltos continue; } @@ -443,6 +546,36 @@ void ast_print(ASTNode *node, const char *prefix, int is_last) { } break; + case NODE_CLASS_DEF: + printf("NODE_CLASS_DEF(\"%s\")\n", node->data.class_def.name); + for (int i = 0; i < node->data.class_def.method_count; i++) { + ast_print(node->data.class_def.methods[i], new_prefix, + i == node->data.class_def.method_count - 1); + } + break; + + case NODE_DOT_ACCESS: + printf("NODE_DOT_ACCESS(.%s)\n", node->data.dot_access.field); + ast_print(node->data.dot_access.object, new_prefix, 1); + break; + + case NODE_DOT_ASSIGN: + printf("NODE_DOT_ASSIGN(.%s)\n", node->data.dot_assign.field); + ast_print(node->data.dot_assign.object, new_prefix, 0); + ast_print(node->data.dot_assign.value, new_prefix, 1); + break; + + case NODE_METHOD_CALL: + printf("NODE_METHOD_CALL(.%s, %d args)\n", node->data.method_call.method, + node->data.method_call.arg_count); + ast_print(node->data.method_call.object, new_prefix, + node->data.method_call.arg_count == 0); + for (int i = 0; i < node->data.method_call.arg_count; i++) { + ast_print(node->data.method_call.args[i], new_prefix, + i == node->data.method_call.arg_count - 1); + } + break; + default: printf("UNKNOWN\n"); break; diff --git a/src/memory/gc.h b/src/memory/gc.h index f8743bf..1188294 100644 --- a/src/memory/gc.h +++ b/src/memory/gc.h @@ -4,6 +4,7 @@ #include "../objects/object.h" #include "allocator.h" + void gc_collect(JLANG_memory_allocator *allocPtr, size_t *roots, int root_count) { // Stage 1. Mark blocks @@ -29,67 +30,79 @@ void gc_collect(JLANG_memory_allocator *allocPtr, size_t *roots, objPtr->data.string_val.chars - sizeof(JLANG_metadata)); itemsHeader->marked = 1; - } - } + } else if (objPtr->type == OBJ_INSTANCE) { + JLANG_metadata *namesHeader = + (JLANG_metadata *)((char *)allocPtr->memory + + objPtr->data.instance_val.field_names - + sizeof(JLANG_metadata)); + namesHeader->marked = 1; - // Stage 2. Sweep memory - JLANG_metadata *currentHead = (JLANG_metadata *)allocPtr->memory; - while (currentHead->size != 0) { - size_t blockOffset = (char *)currentHead - (char *)allocPtr->memory; - - if (currentHead->marked == 0 && currentHead->in_use) { - // Free block - JLANG_free(allocPtr, blockOffset + sizeof(JLANG_metadata)); + JLANG_metadata *valuesHeader = + (JLANG_metadata *)((char *)allocPtr->memory + + objPtr->data.instance_val.field_values - + sizeof(JLANG_metadata)); + valuesHeader->marked = 1; + } } - // Reset mark - currentHead->marked = 0; + // Stage 2. Sweep memory + JLANG_metadata *currentHead = (JLANG_metadata *)allocPtr->memory; + while (currentHead->size != 0) { + size_t blockOffset = (char *)currentHead - (char *)allocPtr->memory; - currentHead = (JLANG_metadata *)((char *)currentHead + currentHead->size + - sizeof(JLANG_metadata)); - } + if (currentHead->marked == 0 && currentHead->in_use) { + // Free block + JLANG_free(allocPtr, blockOffset + sizeof(JLANG_metadata)); + } - // Stage 3. Join free blocks - currentHead = (JLANG_metadata *)allocPtr->memory; - while (currentHead->size != 0) { + // Reset mark + currentHead->marked = 0; - if (currentHead->in_use == 0) { - // iterate until last next free block - JLANG_metadata *nextHeader = - (JLANG_metadata *)((char *)currentHead + sizeof(JLANG_metadata) + - currentHead->size); - JLANG_metadata *lastHeader = currentHead; - while (1) { - if (nextHeader->in_use == 1 || nextHeader->size == 0) { - // Volvemos atrás - break; + currentHead = (JLANG_metadata *)((char *)currentHead + currentHead->size + + sizeof(JLANG_metadata)); + } + + // Stage 3. Join free blocks + currentHead = (JLANG_metadata *)allocPtr->memory; + while (currentHead->size != 0) { + + if (currentHead->in_use == 0) { + // iterate until last next free block + JLANG_metadata *nextHeader = + (JLANG_metadata *)((char *)currentHead + sizeof(JLANG_metadata) + + currentHead->size); + JLANG_metadata *lastHeader = currentHead; + while (1) { + if (nextHeader->in_use == 1 || nextHeader->size == 0) { + // Volvemos atrás + break; + } + + lastHeader = nextHeader; + nextHeader = + (JLANG_metadata *)((char *)nextHeader + sizeof(JLANG_metadata) + + nextHeader->size); } - lastHeader = nextHeader; - nextHeader = - (JLANG_metadata *)((char *)nextHeader + sizeof(JLANG_metadata) + - nextHeader->size); + // Make from currentHead to nextHeader + nextHeader(size) one block + size_t startIndex = (char *)currentHead - (char *)allocPtr->memory; + size_t endIndex = + ((char *)lastHeader + sizeof(JLANG_metadata) + lastHeader->size) - + (char *)allocPtr->memory; + + // Set to 0 + for (int i = 0; i < (endIndex - startIndex); i++) { + allocPtr->memory[i + startIndex] = 0; + } + + // Create valid header + currentHead = (JLANG_metadata *)((char *)allocPtr->memory + startIndex); + currentHead->size = (endIndex - startIndex) - sizeof(JLANG_metadata); } - // Make from currentHead to nextHeader + nextHeader(size) one block - size_t startIndex = (char *)currentHead - (char *)allocPtr->memory; - size_t endIndex = - ((char *)lastHeader + sizeof(JLANG_metadata) + lastHeader->size) - - (char *)allocPtr->memory; - - // Set to 0 - for (int i = 0; i < (endIndex - startIndex); i++) { - allocPtr->memory[i + startIndex] = 0; - } - - // Create valid header - currentHead = (JLANG_metadata *) ((char *)allocPtr->memory + startIndex); - currentHead->size = (endIndex - startIndex) - sizeof(JLANG_metadata); + currentHead = (JLANG_metadata *)((char *)currentHead + currentHead->size + + sizeof(JLANG_metadata)); } - - currentHead = (JLANG_metadata *)((char *)currentHead + currentHead->size + - sizeof(JLANG_metadata)); } -} #endif diff --git a/src/objects/object.h b/src/objects/object.h index ab24f10..2f5f09b 100644 --- a/src/objects/object.h +++ b/src/objects/object.h @@ -3,11 +3,10 @@ #include "../memory/allocator.h" - #define JLANG_RESOLVE(alloc, offset) \ ((void *)(((JLANG_memory_allocator *)(alloc))->memory + (offset))) -typedef enum { OBJ_INT, OBJ_FLOAT, OBJ_STRING, OBJ_LIST, OBJ_NONE } ObjectType; +typedef enum { OBJ_INT, OBJ_FLOAT, OBJ_STRING, OBJ_LIST, OBJ_NONE, OBJ_INSTANCE } ObjectType; typedef struct Object { ObjectType type; @@ -23,9 +22,38 @@ typedef struct Object { int count; int capacity; } list_val; + struct { + int class_index; // indice en Chunk.classes[] + size_t field_names; // heap offset -> array de int + size_t field_values; // heap offset -> array de Value + int field_count; + int field_capacity; + } instance_val; } data; } Object; +size_t obj_new_instance(void *allocator, int class_index, int capacity, size_t value_size) { + size_t offset = JLANG_malloc(allocator, sizeof(Object)); + Object *objPtr = (Object *)JLANG_RESOLVE(allocator, offset); + objPtr->type = OBJ_INSTANCE; + objPtr->data.instance_val.class_index = class_index; + objPtr->data.instance_val.field_count = 0; + objPtr->data.instance_val.field_capacity = capacity; + + // alloc array de nombres + size_t namesOffset = JLANG_malloc(allocator, capacity * sizeof(int)); + objPtr = (Object *) JLANG_RESOLVE(allocator, offset); // re-resolve + + // alloc array de valores + size_t valuesOffset = JLANG_malloc(allocator, capacity * value_size); + objPtr = (Object *) JLANG_RESOLVE(allocator, offset); // re-resolve + + objPtr->data.instance_val.field_names = namesOffset; + objPtr->data.instance_val.field_values = valuesOffset; + + return offset; +} + size_t obj_new_int(void *allocator, int value) { // Object *objPtr = (Object *)JLANG_malloc(allocator, sizeof(Object)); size_t offset = JLANG_malloc(allocator, sizeof(Object)); @@ -91,10 +119,16 @@ void obj_free(void *allocator, size_t offset) { JLANG_free(allocator, obj->data.list_val.items); } + if (obj->type == OBJ_INSTANCE) { + JLANG_free(allocator, obj->data.instance_val.field_names); + JLANG_free(allocator, obj->data.instance_val.field_values); + } + JLANG_free(allocator, offset); } -void obj_print(void *allocator, size_t offset, const char *preffix, const char *suffix) { +void obj_print(void *allocator, size_t offset, const char *preffix, + const char *suffix) { Object *obj = (Object *)JLANG_RESOLVE(allocator, offset); switch (obj->type) {