Files
j-lang/docs/roadmap.md

449 lines
11 KiB
Markdown
Raw Permalink Normal View History

2026-02-15 20:48:01 +01:00
# 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`