# j-lang: Roadmap detallado Guia de implementacion fase por fase. Cada fase construye sobre la anterior. ``` src/ memory/ -> Fase 1 y 5 objects/ -> Fase 2 frontend/ -> Fase 3 vm/ -> Fase 4 main.c -> Punto de entrada (une todo) ``` --- ## Fase 1: Gestion de Memoria **Objetivo:** Tener un `malloc` y `free` propios que gestionen un heap simulado con metadatos compactos. **Archivos:** `src/memory/allocator.h` **Estado actual:** Ya existe una implementacion en `mem-heap/src/allocator.h` con: - `CMA_metadata` (struct con `size` y `in_use`) - `CMA_CreateAllocator()` - crea un heap de 1KB - `CMA_malloc()` - asigna bloques (solo append, no reutiliza huecos) - `CMA_used()` - calcula bytes usados - `CMA_last_free()` - encuentra el primer bloque libre - `CMA_visualize()` - dibuja el heap en consola con colores ### Tareas #### 1.1 Copiar allocator al proyecto - Copiar `mem-heap/src/allocator.h` a `src/memory/allocator.h` - Verificar que compila desde `main.c` con `#include "memory/allocator.h"` #### 1.2 Implementar `CMA_free` Liberar un bloque marcandolo como no-usado. ```c // Firma void CMA_free(void *allocator, void *ptr); // Logica: // 1. ptr apunta al payload, retroceder sizeof(CMA_metadata) para llegar al header // 2. Poner header->in_use = 0 // 3. Opcionalmente: poner a cero los bytes del payload (memset) ``` #### 1.3 Reutilizar bloques liberados (first-fit) Actualmente `CMA_malloc` solo hace append al final. Hay que recorrer el heap buscando bloques libres que quepan. ```c // En CMA_malloc, antes de ir al final: // 1. Recorrer bloques desde el inicio // 2. Si encuentras uno con in_use == 0 && size >= requested_size -> reutilizarlo // 3. Si no hay ninguno, seguir con el append actual ``` #### 1.4 Compactar la cabecera (opcional, mejora) El readme menciona compactar Size + Marked + InUse en menos bytes. La idea: - Usar bitfields o empaquetar en un `uint32_t`: - Bits 0-29: size (hasta ~1GB) - Bit 30: marked (para el GC, Fase 5) - Bit 31: in_use ```c typedef struct { uint32_t header; // size:30 | marked:1 | in_use:1 } CMA_metadata_compact; #define META_SIZE(h) ((h).header >> 2) #define META_MARKED(h) (((h).header >> 1) & 1) #define META_INUSE(h) ((h).header & 1) ``` ### Criterio de terminado - `CMA_malloc` asigna bloques correctamente - `CMA_free` libera bloques - `CMA_malloc` reutiliza bloques liberados - `CMA_visualize` muestra bloques libres vs ocupados correctamente - Test: malloc -> free -> malloc reutiliza el mismo espacio --- ## Fase 2: Modelo de Objetos **Objetivo:** Definir como se representan los valores del lenguaje (numeros, strings, listas) en memoria C. **Archivos:** `src/objects/object.h` **Dependencias:** Fase 1 (los objetos se crean con `CMA_malloc`) ### Tareas #### 2.1 Definir los tipos ```c typedef enum { OBJ_INT, OBJ_FLOAT, OBJ_STRING, OBJ_LIST, OBJ_NONE // equivalente a None/null } ObjectType; ``` #### 2.2 Definir el struct Object Enfoque con tagged union: ```c typedef struct Object { ObjectType type; union { int int_val; // OBJ_INT double float_val; // OBJ_FLOAT struct { // OBJ_STRING char *chars; int length; } string_val; struct { // OBJ_LIST struct Object **items; int count; int capacity; } list_val; } data; } Object; ``` #### 2.3 Funciones constructoras Cada tipo necesita una funcion que cree el objeto usando el allocator: ```c Object* obj_new_int(void *allocator, int value); Object* obj_new_string(void *allocator, const char *str); Object* obj_new_list(void *allocator); ``` #### 2.4 Funcion de impresion ```c void obj_print(Object *obj); // OBJ_INT -> printf("%d", obj->data.int_val) // OBJ_STRING -> printf("%s", obj->data.string_val.chars) // OBJ_LIST -> printf("["); ...cada elemento...; printf("]") ``` ### Criterio de terminado - Se pueden crear objetos de cada tipo con el allocator - `obj_print` los muestra correctamente - Test: crear int(42), string("hola"), lista con ambos, imprimir todo --- ## Fase 3: Front-End (Lexer y Parser) **Objetivo:** Convertir texto fuente (`.j`) en un AST que la VM pueda ejecutar. **Archivos:** `src/frontend/lexer.h`, `src/frontend/parser.h` **Dependencias:** Fase 2 (el parser crea nodos AST que referencian tipos de Object) ### Tareas #### 3.1 Lexer (Tokenizador) Convierte texto en una lista de tokens. ```c typedef enum { // Literales TOK_INT, // 42 TOK_STRING, // "hola" // Identificadores y keywords TOK_ID, // x, foo, mi_var TOK_PRINT, // print (keyword) TOK_IF, // if TOK_WHILE, // while // Operadores TOK_ASSIGN, // = TOK_PLUS, // + TOK_MINUS, // - TOK_STAR, // * TOK_SLASH, // / TOK_EQ, // == TOK_NEQ, // != TOK_LT, // < TOK_GT, // > // Delimitadores TOK_LPAREN, // ( TOK_RPAREN, // ) TOK_COLON, // : TOK_NEWLINE, // \n (significativo, como en Python) TOK_INDENT, // aumento de indentacion TOK_DEDENT, // reduccion de indentacion TOK_EOF } TokenType; typedef struct { TokenType type; char *value; // texto literal del token int line; // linea del fuente (para errores) } Token; // Firma principal Token* tokenize(const char *source, int *token_count); ``` Empezar simple: solo `TOK_INT`, `TOK_ID`, `TOK_ASSIGN`, `TOK_NEWLINE`, `TOK_EOF`. Ir agregando tokens conforme se necesiten. #### 3.2 Parser (AST) Convierte tokens en un arbol. ```c typedef enum { NODE_INT_LIT, // literal entero: 42 NODE_STRING_LIT, // literal string: "hola" NODE_VAR, // referencia a variable: x NODE_ASSIGN, // asignacion: x = expr NODE_BINOP, // operacion binaria: a + b NODE_PRINT, // print(expr) NODE_IF, // if cond: bloque NODE_WHILE, // while cond: bloque NODE_BLOCK, // secuencia de statements } NodeType; typedef struct ASTNode { NodeType type; union { int int_val; // NODE_INT_LIT char *string_val; // NODE_STRING_LIT, NODE_VAR struct { char *name; struct ASTNode *value; } assign; // NODE_ASSIGN struct { char op; struct ASTNode *left; struct ASTNode *right; } binop; // NODE_BINOP struct { struct ASTNode *expr; } print; // NODE_PRINT struct { struct ASTNode **stmts; int count; } block; // NODE_BLOCK } data; } ASTNode; // Firma principal ASTNode* parse(Token *tokens, int token_count); ``` #### 3.3 Gramatica minima inicial Empezar con un subconjunto muy pequeno: ``` programa = { statement NEWLINE } statement = ID "=" expr (asignacion) | "print" expr (impresion) expr = term { ("+" | "-") term } term = INT | ID ``` Esto permite ejecutar cosas como: ``` x = 10 y = 20 z = x + y print z ``` ### Criterio de terminado - El lexer tokeniza `x = 10\nprint x` correctamente - El parser genera un AST valido a partir de esos tokens - Test: tokenizar y parsear `simple.j`, imprimir el arbol resultante --- ## Fase 4: Evaluador / VM **Objetivo:** Recorrer el AST y ejecutar las instrucciones. **Archivos:** `src/vm/eval.h` **Dependencias:** Fase 2 (crea Objects), Fase 3 (recibe un AST) ### Tareas #### 4.1 Tabla de variables (Environment) ```c typedef struct { char *name; Object *value; } Variable; typedef struct { Variable vars[256]; // simple, tamanio fijo por ahora int count; } Environment; // Buscar o crear variable Object* env_get(Environment *env, const char *name); void env_set(Environment *env, const char *name, Object *value); ``` #### 4.2 Funcion eval recursiva ```c Object* eval(ASTNode *node, Environment *env, void *allocator); // Logica por tipo de nodo: // NODE_INT_LIT -> obj_new_int(allocator, node->data.int_val) // NODE_VAR -> env_get(env, node->data.string_val) // NODE_ASSIGN -> evaluar valor, env_set(name, resultado) // NODE_BINOP -> evaluar left y right, operar segun op // NODE_PRINT -> evaluar expr, obj_print(resultado) // NODE_BLOCK -> evaluar cada statement en orden ``` #### 4.3 Flujo completo en main.c ```c int main(int argc, char *argv[]) { // 1. Leer archivo .j char *source = read_file(argv[1]); // 2. Tokenizar int token_count; Token *tokens = tokenize(source, &token_count); // 3. Parsear ASTNode *program = parse(tokens, token_count); // 4. Ejecutar void *allocator = CMA_CreateAllocator(); Environment env = {0}; eval(program, &env, allocator); return 0; } ``` ### Criterio de terminado - `simple.j` con `x = 1` se ejecuta sin errores - Un script con `x = 10\ny = 20\nprint x + y` imprime `30` - Las variables se almacenan y recuperan correctamente --- ## Fase 5: Recolector de Basura (GC) **Objetivo:** Liberar automaticamente objetos que ya no son accesibles. **Archivos:** `src/memory/gc.h` **Dependencias:** Fase 1 (usa `CMA_free`), Fase 2 (recorre Objects), Fase 4 (accede al Environment) ### Tareas #### 5.1 Agregar bit `marked` al metadata Si se compacto la cabecera en Fase 1.4, ya esta. Si no, agregar un campo: ```c typedef struct { size_t size; int in_use; int marked; // <-- nuevo } CMA_metadata; ``` #### 5.2 Mark (Marcar) Recorrer todos los objetos alcanzables desde las variables del Environment y marcarlos. ```c void gc_mark_object(Object *obj); // 1. Obtener el header del objeto (retroceder sizeof(CMA_metadata)) // 2. Poner marked = 1 // 3. Si es OBJ_LIST, marcar recursivamente cada elemento // 4. Si es OBJ_STRING, marcar el buffer de chars void gc_mark(Environment *env); // Para cada variable en env: gc_mark_object(variable.value) ``` #### 5.3 Sweep (Barrer) Recorrer todo el heap linealmente. Si un bloque tiene `in_use == 1` y `marked == 0`, liberarlo. ```c void gc_sweep(void *allocator); // 1. Empezar desde el inicio del heap // 2. Para cada bloque: // - Si in_use && !marked -> CMA_free // - Si marked -> quitar marked (resetear para el proximo ciclo) // 3. Avanzar al siguiente bloque con (header + sizeof(CMA_metadata) + size) ``` #### 5.4 Integrar con el evaluador ```c // En eval(), despues de N asignaciones o cuando quede poca memoria: void gc_collect(void *allocator, Environment *env) { gc_mark(env); gc_sweep(allocator); } ``` ### Criterio de terminado - Crear objetos, perder la referencia, ejecutar GC -> la memoria se libera - `CMA_visualize` muestra bloques liberados tras el GC - Test: crear 100 objetos en un loop, verificar que la memoria no crece indefinidamente --- ## Orden de implementacion recomendado ``` Fase 1.1 (copiar allocator) | Fase 1.2 (CMA_free) | Fase 1.3 (reutilizar bloques) | Fase 2 (model de objetos) | Fase 3.1 (lexer basico) | Fase 3.2 (parser basico) | Fase 4 (eval + main.c) <- aqui ya puedes ejecutar simple.j | Fase 1.4 (compactar cabecera) <- opcional, mejora | Fase 5 (GC) ``` ## Test de integracion final Cuando todo este listo, este script deberia funcionar: ```python x = 10 y = 20 z = x + y print z ``` Salida esperada: `30`