first commit
This commit is contained in:
448
docs/roadmap.md
Normal file
448
docs/roadmap.md
Normal file
@@ -0,0 +1,448 @@
|
||||
# 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`
|
||||
1
projects/simple.j
Normal file
1
projects/simple.j
Normal file
@@ -0,0 +1 @@
|
||||
x = 1
|
||||
52
readme.md
Normal file
52
readme.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# j-lang
|
||||
|
||||
La idea de j-lang es crear un "proto-lenguaje" parecido a python pero implementado desde 0 para validar y aprender más sobre la gestión de memoria.
|
||||
|
||||
Actualmente en `mem-heap\src\allocator.h` ya hay una implementeción de un Memory Allocator casi funcional.
|
||||
|
||||
## 🗺️ Hoja de Ruta: Proyecto Proto-Lenguaje
|
||||
Esta ruta va desde lo más bajo (la memoria) hasta lo más alto (ejecutar código).
|
||||
|
||||
### Fase 1: El Cimiento (Gestión de Memoria) 🏗️
|
||||
Objetivo: Tener un malloc y free propios que gestionen metadatos compactos.
|
||||
|
||||
Estado: ¡Ya estás aquí!
|
||||
|
||||
Tareas clave:
|
||||
- [ ] Terminar CMA_malloc con la cabecera compactada (Size + Marked + InUse).
|
||||
- [ ] Implementar una función CMA_free que pueda liberar un bloque específico.
|
||||
|
||||
### Fase 2: El Modelo de Objetos (Object Model) 📦
|
||||
Objetivo: Definir cómo se ve un número, una cadena o una lista dentro de tu memoria C.
|
||||
Conexión: Cada objeto de tu lenguaje será un struct en C que comienza con tu CMA_metadata.
|
||||
|
||||
Tareas clave:
|
||||
- [ ] Crear un enum para los tipos (ENTERO, STRING, LISTA).
|
||||
- [ ] Definir el struct Object genérico que envuelve tus datos.
|
||||
|
||||
### Fase 3: El Front-End (Lexer y Parser) 📖
|
||||
Objetivo: Convertir el texto del código fuente en algo que C entienda.
|
||||
|
||||
Tareas clave:
|
||||
- [ ] Lexer (Tokenizador): Romper el texto x = 10 en fichas: [ID:x], [OP:=], [NUM:10].
|
||||
- [ ] Parser: Organizar esas fichas en un Árbol de Sintaxis Abstracta (AST). Por ejemplo, un nodo "Asignación" que tiene un hijo "x" y otro "10".
|
||||
|
||||
### Fase 4: El Motor (Evaluador o VM) ⚙️
|
||||
Objetivo: Recorrer el árbol y "hacer" lo que dice.
|
||||
|
||||
Tareas clave:
|
||||
- [ ] Crear una función recursiva eval(nodo) que ejecute la lógica.
|
||||
|
||||
Si es un nodo SUMA, suma los hijos. Si es un nodo IMPRIMIR, muestra en pantalla.
|
||||
|
||||
### Fase 5: El Recolector de Basura (Garbage Collector) 🧹
|
||||
Objetivo: Automatizar la limpieza.
|
||||
|
||||
Tareas clave:
|
||||
- [ ] Implementar Mark (Marcar): Recorrer todos los objetos accesibles desde tus variables y poner el bit Marked a 1.
|
||||
- [ ] Implementar Sweep (Barrer): Recorrer todo el heap linealmente (usando tu función next_block). Si un bloque tiene Marked == 0 y InUse == 1, llamar a CMA_free.
|
||||
|
||||
## Estructura del proyecto
|
||||
|
||||
- vm: maquina virtual de j-lang
|
||||
- projects: carpeta con scripts en j-lang
|
||||
3
src/main.c
Normal file
3
src/main.c
Normal file
@@ -0,0 +1,3 @@
|
||||
int main() {
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user