Add bytecode VM backend (compile AST to bytecodes + stack-based VM)
New execution mode: ./run vm <file.j> compiles AST to bytecodes and runs them in a while/switch loop. Ints/floats live on the stack (no heap allocation), ~7.7x faster than the tree-walking interpreter. Implements: opcodes, compiler with backpatching (if/while), stack VM with arithmetic, comparisons, variables, strings, and print/println. Reorganizes backend into src/backend/eval/ and src/backend/bytecode/.
This commit is contained in:
6
projects/vm_simple.j
Normal file
6
projects/vm_simple.j
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
x = 0
|
||||||
|
while x < 10:
|
||||||
|
x = x + 1
|
||||||
|
|
||||||
|
if x > 1000000:
|
||||||
|
println("OK")
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
x = 0
|
x = 0
|
||||||
while x < 10000000:
|
while x < 1000000000:
|
||||||
x = x + 1
|
x = x + 1
|
||||||
print x
|
println(x)
|
||||||
|
|||||||
266
readme.md
266
readme.md
@@ -1,52 +1,240 @@
|
|||||||
# j-lang
|
# 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.
|
Un proto-lenguaje con sintaxis inspirada en Python, implementado desde cero en C. El objetivo es aprender sobre gestion de memoria, tokenizacion, parsing y evaluacion de un lenguaje de programacion.
|
||||||
|
|
||||||
Actualmente en `mem-heap\src\allocator.h` ya hay una implementeción de un Memory Allocator casi funcional.
|
## Estado actual
|
||||||
|
|
||||||
## 🗺️ Hoja de Ruta: Proyecto Proto-Lenguaje
|
Las 5 fases del interprete estan implementadas y funcionando:
|
||||||
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.
|
Codigo fuente (.j)
|
||||||
|
|
|
||||||
|
[LEXER] src/frontend/lexer.h
|
||||||
|
|
|
||||||
|
Tokens
|
||||||
|
|
|
||||||
|
[PARSER] src/frontend/parser.h
|
||||||
|
|
|
||||||
|
AST
|
||||||
|
|
|
||||||
|
[EVAL] src/vm/eval.h
|
||||||
|
|
|
||||||
|
Ejecucion + GC
|
||||||
|
```
|
||||||
|
|
||||||
Estado: ¡Ya estás aquí!
|
### Que funciona
|
||||||
|
|
||||||
Tareas clave:
|
- **Variables y asignacion:** `x = 10`
|
||||||
- [ ] Terminar CMA_malloc con la cabecera compactada (Size + Marked + InUse).
|
- **Aritmetica:** `+`, `-`, `*`, `/` con enteros
|
||||||
- [ ] Implementar una función CMA_free que pueda liberar un bloque específico.
|
- **Comparaciones:** `<`, `>`
|
||||||
|
- **Strings:** literales, concatenacion con `+`, `len()`
|
||||||
|
- **Control de flujo:** `if` y `while` con bloques indentados (estilo Python)
|
||||||
|
- **Funciones built-in:** `print()`, `println()`, `len()`
|
||||||
|
- **Llamadas a funciones** con multiples argumentos separados por `,`
|
||||||
|
- **Expresiones con parentesis:** `2 * (4 - 2)`
|
||||||
|
- **Numeros negativos:** `-300`
|
||||||
|
- **Comentarios:** `// esto es un comentario`
|
||||||
|
|
||||||
### Fase 2: El Modelo de Objetos (Object Model) 📦
|
### Ejemplo
|
||||||
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).
|
x = 0
|
||||||
- [ ] Definir el struct Object genérico que envuelve tus datos.
|
while x < 10:
|
||||||
|
x = x + 1
|
||||||
|
|
||||||
### Fase 3: El Front-End (Lexer y Parser) 📖
|
if x > 9:
|
||||||
Objetivo: Convertir el texto del código fuente en algo que C entienda.
|
println("fin")
|
||||||
|
```
|
||||||
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
|
## Estructura del proyecto
|
||||||
|
|
||||||
- vm: maquina virtual de j-lang
|
```
|
||||||
- projects: carpeta con scripts en j-lang
|
src/
|
||||||
|
frontend/
|
||||||
|
lexer.h Tokenizador: texto -> tokens
|
||||||
|
parser.h Parser: tokens -> AST
|
||||||
|
memory/
|
||||||
|
allocator.h Memory allocator custom (heap simulado)
|
||||||
|
gc.h Garbage collector (mark-and-sweep)
|
||||||
|
objects/
|
||||||
|
object.h Modelo de objetos (int, float, string, list)
|
||||||
|
vm/
|
||||||
|
eval.h Evaluador: recorre el AST y ejecuta
|
||||||
|
main.c Punto de entrada
|
||||||
|
projects/ Scripts de ejemplo en .j
|
||||||
|
docs/
|
||||||
|
roadmap.md Roadmap detallado de implementacion
|
||||||
|
```
|
||||||
|
|
||||||
|
### Memory allocator
|
||||||
|
|
||||||
|
Heap simulado sobre un array de bytes con metadatos por bloque (`size`, `in_use`, `marked`). Soporta asignacion, liberacion, reutilizacion de bloques libres (first-fit) y crecimiento automatico cuando se queda sin espacio.
|
||||||
|
|
||||||
|
### Garbage collector
|
||||||
|
|
||||||
|
Mark-and-sweep: marca los objetos alcanzables desde las variables del environment, barre los no marcados y fusiona bloques libres contiguos.
|
||||||
|
|
||||||
|
### Modelo de objetos
|
||||||
|
|
||||||
|
Los valores del lenguaje se representan como `Object` con tagged union. Tipos soportados: `OBJ_INT`, `OBJ_FLOAT`, `OBJ_STRING`, `OBJ_LIST`, `OBJ_NONE`. Los objetos viven en el heap custom y se referencian por offset (no punteros absolutos).
|
||||||
|
|
||||||
|
## Compilar y ejecutar
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gcc src/main.c -o run
|
||||||
|
./run projects/sum.j
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Roadmap: que falta para hacer un juego 2D con JLang
|
||||||
|
|
||||||
|
Para poder escribir un juego 2D tipo "mover un personaje por pantalla, disparar, colisiones" con JLang, harian falta estos bloques:
|
||||||
|
|
||||||
|
### 1. Funciones de usuario
|
||||||
|
|
||||||
|
Lo mas urgente. Sin funciones no se puede organizar nada.
|
||||||
|
|
||||||
|
```
|
||||||
|
fn update(dt):
|
||||||
|
player_x = player_x + speed * dt
|
||||||
|
|
||||||
|
fn draw():
|
||||||
|
draw_rect(player_x, player_y, 32, 32)
|
||||||
|
```
|
||||||
|
|
||||||
|
Implica: nuevo token `fn`, nodo `NODE_FUNC_DEF` en el AST, almacenar el cuerpo de la funcion en el environment, y un mecanismo de scopes (variables locales vs globales).
|
||||||
|
|
||||||
|
### 2. Return
|
||||||
|
|
||||||
|
Las funciones necesitan devolver valores.
|
||||||
|
|
||||||
|
```
|
||||||
|
fn distance(x1, y1, x2, y2):
|
||||||
|
dx = x1 - x2
|
||||||
|
dy = y1 - y2
|
||||||
|
return sqrt(dx * dx + dy * dy)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Structs o clases
|
||||||
|
|
||||||
|
Para representar entidades del juego (jugador, enemigos, balas...).
|
||||||
|
|
||||||
|
```
|
||||||
|
class Entity:
|
||||||
|
x = 0
|
||||||
|
y = 0
|
||||||
|
w = 32
|
||||||
|
h = 32
|
||||||
|
|
||||||
|
player = Entity()
|
||||||
|
player.x = 100
|
||||||
|
player.y = 200
|
||||||
|
```
|
||||||
|
|
||||||
|
Implica: acceso a campos con `.`, constructor, almacenar la definicion de la clase como un objeto mas en el heap.
|
||||||
|
|
||||||
|
### 4. Listas funcionales
|
||||||
|
|
||||||
|
Las listas ya existen como tipo (`OBJ_LIST`) pero no hay sintaxis para usarlas. Se necesitan para manejar colecciones de entidades.
|
||||||
|
|
||||||
|
```
|
||||||
|
enemies = [Enemy(), Enemy(), Enemy()]
|
||||||
|
append(enemies, Enemy())
|
||||||
|
i = 0
|
||||||
|
while i < len(enemies):
|
||||||
|
update(enemies[i])
|
||||||
|
i = i + 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Implica: sintaxis `[...]`, acceso por indice `lista[i]`, `append()`, `len()` para listas.
|
||||||
|
|
||||||
|
### 5. Else / elif
|
||||||
|
|
||||||
|
Imprescindible para logica de juego.
|
||||||
|
|
||||||
|
```
|
||||||
|
if key == "left":
|
||||||
|
player_x = player_x - speed
|
||||||
|
elif key == "right":
|
||||||
|
player_x = player_x + speed
|
||||||
|
else:
|
||||||
|
speed = 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. For loops
|
||||||
|
|
||||||
|
Iterar de forma mas limpia que con `while`.
|
||||||
|
|
||||||
|
```
|
||||||
|
for enemy in enemies:
|
||||||
|
draw_rect(enemy.x, enemy.y, enemy.w, enemy.h)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Operadores que faltan
|
||||||
|
|
||||||
|
- `%` (modulo) - util para animaciones ciclicas, wrapping
|
||||||
|
- `==`, `!=` (ya tokenizados pero no evaluados completamente)
|
||||||
|
- `<=`, `>=`
|
||||||
|
- `and`, `or`, `not` - operadores logicos
|
||||||
|
- `+=`, `-=` - azucar sintactico
|
||||||
|
|
||||||
|
### 8. Floats funcionales
|
||||||
|
|
||||||
|
El tipo `OBJ_FLOAT` existe pero no se puede usar desde el lenguaje. Para un juego se necesita aritmetica de punto flotante para posiciones, velocidades, delta time, etc.
|
||||||
|
|
||||||
|
```
|
||||||
|
player_x = 100.0
|
||||||
|
speed = 2.5
|
||||||
|
player_x = player_x + speed * dt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9. Libreria grafica (FFI a C)
|
||||||
|
|
||||||
|
El punto critico. JLang necesita poder llamar a una libreria grafica en C como SDL2 o raylib. Hay dos caminos:
|
||||||
|
|
||||||
|
**Opcion A: Built-in functions (mas facil)**
|
||||||
|
Registrar funciones C directamente en el evaluador, como ya se hace con `print`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
// En el eval, junto a print/println:
|
||||||
|
if (strcmp(name, "draw_rect") == 0) { SDL_RenderFillRect(...); }
|
||||||
|
if (strcmp(name, "key_pressed") == 0) { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Opcion B: FFI generico (mas ambicioso)**
|
||||||
|
Un sistema para enlazar funciones C arbitrarias desde JLang.
|
||||||
|
|
||||||
|
Las funciones minimas para un juego serian:
|
||||||
|
|
||||||
|
| Funcion | Descripcion |
|
||||||
|
|---|---|
|
||||||
|
| `create_window(w, h, title)` | Crear ventana |
|
||||||
|
| `clear()` | Limpiar pantalla |
|
||||||
|
| `draw_rect(x, y, w, h, r, g, b)` | Dibujar rectangulo |
|
||||||
|
| `draw_image(path, x, y)` | Dibujar imagen/sprite |
|
||||||
|
| `present()` | Mostrar frame |
|
||||||
|
| `key_pressed(key)` | Consultar tecla |
|
||||||
|
| `get_dt()` | Delta time entre frames |
|
||||||
|
| `random(min, max)` | Numero aleatorio |
|
||||||
|
|
||||||
|
### 10. Funciones matematicas
|
||||||
|
|
||||||
|
`sqrt()`, `sin()`, `cos()`, `abs()`, `random()`. Todas se pueden registrar como built-ins que llamen a `math.h`.
|
||||||
|
|
||||||
|
### Orden sugerido de implementacion
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Funciones de usuario + return (sin esto no se puede hacer nada)
|
||||||
|
2. Else / elif
|
||||||
|
3. Floats funcionales
|
||||||
|
4. Operadores que faltan (%, <=, >=, and, or)
|
||||||
|
5. Listas con sintaxis ([], indexado, append)
|
||||||
|
6. For loops
|
||||||
|
7. Structs o clases
|
||||||
|
8. Built-ins graficos (SDL2/raylib)
|
||||||
|
9. Funciones matematicas
|
||||||
|
10. Juego 2D funcional
|
||||||
|
```
|
||||||
|
|
||||||
|
Los pasos 1-7 son trabajo puro de lenguaje (lexer/parser/eval). El paso 8 es donde JLang toca el mundo real: linkear con SDL2 o raylib a la hora de compilar y exponer las funciones como built-ins en el evaluador.
|
||||||
|
|||||||
222
src/backend/bytecode/compiler.h
Normal file
222
src/backend/bytecode/compiler.h
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
#ifndef JLANG_COMPILER_H
|
||||||
|
#define JLANG_COMPILER_H
|
||||||
|
|
||||||
|
#include "opcodes.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include "../../frontend/parser.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Instruction code[4096]; // bytecodes
|
||||||
|
int code_count;
|
||||||
|
char *constants[256]; // pool de strings literales
|
||||||
|
int const_count;
|
||||||
|
char *names[256]; // tabla de nombres (variables + funciones)
|
||||||
|
int name_count;
|
||||||
|
} Chunk;
|
||||||
|
|
||||||
|
int emit(Chunk* chunk, Instruction instr) {
|
||||||
|
chunk->code[chunk->code_count++] = instr;
|
||||||
|
return chunk->code_count -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int add_constant(Chunk* chunk, char* str) {
|
||||||
|
for (int i=0;i<chunk->const_count; i++){
|
||||||
|
if (strcmp(chunk->constants[i], str) == 0) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk->constants[chunk->const_count++] = str;
|
||||||
|
return chunk->const_count - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instruction make_instruction(OpCode op) {
|
||||||
|
Instruction instr;
|
||||||
|
instr.op = op;
|
||||||
|
return instr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int add_name(Chunk* chunk, char* name) {
|
||||||
|
for (int i=0;i<chunk->name_count; i++){
|
||||||
|
if (strcmp(chunk->names[i], name) == 0) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk->names[chunk->name_count++] = name;
|
||||||
|
return chunk->name_count - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int compile_node(Chunk *chunk, ASTNode* node) {
|
||||||
|
switch (node->type) {
|
||||||
|
case NODE_INT_LIT: {
|
||||||
|
Instruction instr = make_instruction(OP_CONST_INT);
|
||||||
|
instr.operand.int_val = node->data.int_val;
|
||||||
|
return emit(chunk, instr);
|
||||||
|
}
|
||||||
|
case NODE_STRING_LIT: {
|
||||||
|
Instruction instr = make_instruction(OP_CONST_STRING);
|
||||||
|
instr.operand.str_index = add_constant(chunk, node->data.string_val);
|
||||||
|
return emit(chunk, instr);
|
||||||
|
}
|
||||||
|
case NODE_VAR: {
|
||||||
|
Instruction instr = make_instruction(OP_LOAD_VAR);
|
||||||
|
instr.operand.var_index = add_name(chunk, node->data.string_val);
|
||||||
|
return emit(chunk, instr);
|
||||||
|
}
|
||||||
|
case NODE_ASSIGN: {
|
||||||
|
compile_node(chunk, node->data.assign.value);
|
||||||
|
Instruction instr = make_instruction(OP_STORE_VAR);
|
||||||
|
instr.operand.var_index = add_name(chunk, node->data.assign.name);
|
||||||
|
return emit(chunk, instr);
|
||||||
|
}
|
||||||
|
case NODE_CALL: {
|
||||||
|
// Compilar cada argumento y pushear al stack
|
||||||
|
for (int i=0; i<node->data.call.arg_count; i++){
|
||||||
|
compile_node(chunk, node->data.call.args[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Registrar el nombre de la funcion
|
||||||
|
Instruction instr = make_instruction(OP_CALL);
|
||||||
|
instr.operand.call.arg_count = node->data.call.arg_count;
|
||||||
|
instr.operand.call.name_index = add_name(chunk, node->data.call.name);
|
||||||
|
return emit(chunk, instr);
|
||||||
|
}
|
||||||
|
case NODE_BLOCK: {
|
||||||
|
int n = node->data.block.count;
|
||||||
|
|
||||||
|
for (int i=0; i<n; i++){
|
||||||
|
compile_node(chunk, node->data.block.stmts[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case NODE_BINOP: {
|
||||||
|
int leftOffset = compile_node(chunk, node->data.binop.left);
|
||||||
|
int rightOffset = compile_node(chunk, node->data.binop.right);
|
||||||
|
|
||||||
|
OpCode opCode;
|
||||||
|
switch (node->data.binop.op) {
|
||||||
|
case '+':
|
||||||
|
opCode = OP_ADD;
|
||||||
|
break;
|
||||||
|
case '-':
|
||||||
|
opCode = OP_SUB;
|
||||||
|
break;
|
||||||
|
case '*':
|
||||||
|
opCode = OP_MUL;
|
||||||
|
break;
|
||||||
|
case '/':
|
||||||
|
opCode = OP_DIV;
|
||||||
|
break;
|
||||||
|
case '>':
|
||||||
|
opCode = OP_CMP_GT;
|
||||||
|
break;
|
||||||
|
case '<':
|
||||||
|
opCode = OP_CMP_LT;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
emit(chunk, make_instruction(opCode));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case NODE_WHILE: {
|
||||||
|
int loop_start = chunk->code_count;
|
||||||
|
compile_node(chunk, node->data.while_loop.cond);
|
||||||
|
// jump if zero, zero = false
|
||||||
|
Instruction instr = make_instruction(OP_JUMP_IF_ZERO);
|
||||||
|
instr.operand.jump_target = -1;
|
||||||
|
int jump_offset = emit(chunk, instr);
|
||||||
|
|
||||||
|
// compile body
|
||||||
|
compile_node(chunk, node->data.while_loop.body);
|
||||||
|
|
||||||
|
instr = make_instruction(OP_JUMP);
|
||||||
|
instr.operand.jump_target = loop_start;
|
||||||
|
emit(chunk, instr);
|
||||||
|
|
||||||
|
// Bachpatching
|
||||||
|
chunk->code[jump_offset].operand.jump_target = chunk->code_count;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case NODE_IF: {
|
||||||
|
// compile condition
|
||||||
|
compile_node(chunk, node->data.if_statement.cond);
|
||||||
|
|
||||||
|
// add jump if zero
|
||||||
|
Instruction instr = make_instruction(OP_JUMP_IF_ZERO);
|
||||||
|
instr.operand.jump_target = -1;
|
||||||
|
int jump_offset = emit(chunk, instr);
|
||||||
|
|
||||||
|
// compile body
|
||||||
|
compile_node(chunk, node->data.if_statement.body);
|
||||||
|
|
||||||
|
chunk->code[jump_offset].operand.jump_target = chunk->code_count;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Chunk* compile(ASTNode* root) {
|
||||||
|
// Create chunk
|
||||||
|
Chunk* chunk = (Chunk*) malloc(sizeof(Chunk));
|
||||||
|
|
||||||
|
// Set arrays to 0
|
||||||
|
memset(chunk, 0, sizeof(Chunk));
|
||||||
|
|
||||||
|
compile_node(chunk, root);
|
||||||
|
|
||||||
|
Instruction instr;
|
||||||
|
instr.op = OP_HALT;
|
||||||
|
emit(chunk, instr);
|
||||||
|
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_chunk(Chunk* chunk) {
|
||||||
|
printf("=== Names (%d) ===\n", chunk->name_count);
|
||||||
|
for (int i = 0; i < chunk->name_count; i++) {
|
||||||
|
printf(" [%d] %s\n", i, chunk->names[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("=== Constants (%d) ===\n", chunk->const_count);
|
||||||
|
for (int i = 0; i < chunk->const_count; i++) {
|
||||||
|
printf(" [%d] \"%s\"\n", i, chunk->constants[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("=== Bytecode (%d instructions) ===\n", chunk->code_count);
|
||||||
|
for (int i = 0; i < chunk->code_count; i++) {
|
||||||
|
Instruction instr = chunk->code[i];
|
||||||
|
printf("%04d ", i);
|
||||||
|
switch (instr.op) {
|
||||||
|
case OP_CONST_INT: printf("CONST_INT %d", instr.operand.int_val); break;
|
||||||
|
case OP_CONST_STRING: printf("CONST_STRING [%d] \"%s\"", instr.operand.str_index, chunk->constants[instr.operand.str_index]); break;
|
||||||
|
case OP_POP: printf("POP"); break;
|
||||||
|
case OP_ADD: printf("ADD"); break;
|
||||||
|
case OP_SUB: printf("SUB"); break;
|
||||||
|
case OP_MUL: printf("MUL"); break;
|
||||||
|
case OP_DIV: printf("DIV"); break;
|
||||||
|
case OP_NEG: printf("NEG"); break;
|
||||||
|
case OP_CMP_LT: printf("CMP_LT"); break;
|
||||||
|
case OP_CMP_GT: printf("CMP_GT"); break;
|
||||||
|
case OP_LOAD_VAR: printf("LOAD_VAR [%d] %s", instr.operand.var_index, chunk->names[instr.operand.var_index]); break;
|
||||||
|
case OP_STORE_VAR: printf("STORE_VAR [%d] %s", instr.operand.var_index, chunk->names[instr.operand.var_index]); break;
|
||||||
|
case OP_JUMP: printf("JUMP -> %04d", instr.operand.jump_target); break;
|
||||||
|
case OP_JUMP_IF_ZERO: printf("JUMP_IF_ZERO -> %04d", instr.operand.jump_target); break;
|
||||||
|
case OP_CALL: printf("CALL %s(%d args)", chunk->names[instr.operand.call.name_index], instr.operand.call.arg_count); break;
|
||||||
|
case OP_NOP: printf("NOP"); break;
|
||||||
|
case OP_HALT: printf("HALT"); break;
|
||||||
|
default: printf("UNKNOWN op=%d", instr.op); break;
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
printf("=== End ===\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
36
src/backend/bytecode/opcodes.h
Normal file
36
src/backend/bytecode/opcodes.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#ifndef JLANG_OPCODES_H
|
||||||
|
#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_NOP, OP_HALT,
|
||||||
|
} 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;
|
||||||
|
} Instruction;
|
||||||
|
|
||||||
|
#endif
|
||||||
26
src/backend/bytecode/value.h
Normal file
26
src/backend/bytecode/value.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#ifndef JLANG_VALUE_H
|
||||||
|
#define JLANG_VALUE_H
|
||||||
|
|
||||||
|
#include "opcodes.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
VAL_INT,
|
||||||
|
VAL_FLOAT,
|
||||||
|
VAL_OBJ,
|
||||||
|
VAL_NONE,
|
||||||
|
} ValueType;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
ValueType type;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
int int_val;
|
||||||
|
double float_val;
|
||||||
|
size_t heap_offset; // para strings, listas
|
||||||
|
} as;
|
||||||
|
} Value;
|
||||||
|
|
||||||
|
#endif
|
||||||
220
src/backend/bytecode/vm.h
Normal file
220
src/backend/bytecode/vm.h
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
#ifndef JLANG_VM_H
|
||||||
|
#define JLANG_VM_H
|
||||||
|
|
||||||
|
#include "../../memory/gc.h"
|
||||||
|
#include "compiler.h"
|
||||||
|
#include "value.h"
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Chunk *chunk;
|
||||||
|
int ip; // instruction pointer
|
||||||
|
Value stack[1024];
|
||||||
|
int sp; // stack pointer
|
||||||
|
Value vars[256]; // variables por indice
|
||||||
|
int var_set[256]; // 0=no definida, 1=definida
|
||||||
|
JLANG_memory_allocator *allocator;
|
||||||
|
} VM;
|
||||||
|
|
||||||
|
void run_vm(VM *vm) {
|
||||||
|
while (1) {
|
||||||
|
Instruction instr = vm->chunk->code[vm->ip];
|
||||||
|
|
||||||
|
switch (instr.op) {
|
||||||
|
case OP_HALT:
|
||||||
|
// Stop vm
|
||||||
|
return;
|
||||||
|
case OP_JUMP: {
|
||||||
|
// Go to instruction
|
||||||
|
vm->ip = instr.operand.jump_target;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OP_JUMP_IF_ZERO: {
|
||||||
|
// pop from stack
|
||||||
|
Value var1 = vm->stack[--vm->sp];
|
||||||
|
if (var1.as.int_val == 0) {
|
||||||
|
vm->ip = instr.operand.jump_target;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OP_CONST_INT: {
|
||||||
|
// push value to stack
|
||||||
|
Value v = {0};
|
||||||
|
v.type = VAL_INT;
|
||||||
|
v.as.int_val = instr.operand.int_val;
|
||||||
|
vm->stack[vm->sp++] = v;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OP_CONST_STRING: {
|
||||||
|
// Create obj
|
||||||
|
size_t strOffsetHeap = obj_new_string(
|
||||||
|
vm->allocator, vm->chunk->constants[instr.operand.str_index]);
|
||||||
|
|
||||||
|
// Push to stack
|
||||||
|
Value v = {0};
|
||||||
|
v.type = VAL_OBJ;
|
||||||
|
v.as.heap_offset = strOffsetHeap;
|
||||||
|
|
||||||
|
vm->stack[vm->sp++] = v;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OP_STORE_VAR: {
|
||||||
|
// pop del stack
|
||||||
|
Value v = vm->stack[--vm->sp];
|
||||||
|
int idx = instr.operand.var_index;
|
||||||
|
|
||||||
|
// store vm->vars and mark vm->var_set
|
||||||
|
vm->vars[idx] = v;
|
||||||
|
vm->var_set[idx] = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OP_LOAD_VAR: {
|
||||||
|
// get from vm->var
|
||||||
|
int idx = instr.operand.var_index;
|
||||||
|
Value v = vm->vars[idx];
|
||||||
|
// push to stack
|
||||||
|
vm->stack[vm->sp++] = v;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OP_CALL: {
|
||||||
|
int nameIdx = instr.operand.call.name_index;
|
||||||
|
|
||||||
|
char *name = vm->chunk->names[nameIdx];
|
||||||
|
if (strcmp(name, "print") == 0) {
|
||||||
|
Value v = vm->stack[--vm->sp];
|
||||||
|
switch (v.type) {
|
||||||
|
case VAL_INT:
|
||||||
|
printf("%d", v.as.int_val);
|
||||||
|
break;
|
||||||
|
case VAL_OBJ: {
|
||||||
|
// Get object from heap
|
||||||
|
obj_print(vm->allocator, v.as.heap_offset, "", "");
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (strcmp(name, "println") == 0) {
|
||||||
|
Value v = vm->stack[--vm->sp];
|
||||||
|
switch (v.type) {
|
||||||
|
case VAL_INT:
|
||||||
|
printf("%d\n", v.as.int_val);
|
||||||
|
break;
|
||||||
|
case VAL_OBJ: {
|
||||||
|
// Get object from heap
|
||||||
|
obj_print(vm->allocator, v.as.heap_offset, "", "\n");
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (strcmp(name, "debug_heap") == 0) {
|
||||||
|
JLANG_visualize(vm->allocator);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
printf("error: function '%s' not found!\n", name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OP_ADD: {
|
||||||
|
// Pop from stack
|
||||||
|
Value var2 = vm->stack[--vm->sp];
|
||||||
|
Value var1 = vm->stack[--vm->sp];
|
||||||
|
|
||||||
|
Value result = {0};
|
||||||
|
result.type = VAL_INT;
|
||||||
|
result.as.int_val = var1.as.int_val + var2.as.int_val;
|
||||||
|
|
||||||
|
// Push to stack
|
||||||
|
vm->stack[vm->sp++] = result;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OP_SUB: {
|
||||||
|
// Pop from stack
|
||||||
|
Value var2 = vm->stack[--vm->sp];
|
||||||
|
Value var1 = vm->stack[--vm->sp];
|
||||||
|
|
||||||
|
Value result = {0};
|
||||||
|
result.type = VAL_INT;
|
||||||
|
result.as.int_val = var1.as.int_val - var2.as.int_val;
|
||||||
|
|
||||||
|
// Push to stack
|
||||||
|
vm->stack[vm->sp++] = result;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OP_MUL: {
|
||||||
|
// Pop from stack
|
||||||
|
Value var2 = vm->stack[--vm->sp];
|
||||||
|
Value var1 = vm->stack[--vm->sp];
|
||||||
|
|
||||||
|
Value result = {0};
|
||||||
|
result.type = VAL_INT;
|
||||||
|
result.as.int_val = var1.as.int_val * var2.as.int_val;
|
||||||
|
|
||||||
|
// Push to stack
|
||||||
|
vm->stack[vm->sp++] = result;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OP_DIV: {
|
||||||
|
// Pop from stack
|
||||||
|
Value var2 = vm->stack[--vm->sp];
|
||||||
|
Value var1 = vm->stack[--vm->sp];
|
||||||
|
|
||||||
|
Value result = {0};
|
||||||
|
result.type = VAL_INT;
|
||||||
|
result.as.int_val = var1.as.int_val / var2.as.int_val;
|
||||||
|
|
||||||
|
// Push to stack
|
||||||
|
vm->stack[vm->sp++] = result;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OP_CMP_GT: {
|
||||||
|
// Pop from stack
|
||||||
|
Value var2 = vm->stack[--vm->sp];
|
||||||
|
Value var1 = vm->stack[--vm->sp];
|
||||||
|
|
||||||
|
Value result = {0};
|
||||||
|
result.type = VAL_INT;
|
||||||
|
result.as.int_val = var1.as.int_val > var2.as.int_val;
|
||||||
|
|
||||||
|
// Push to stack
|
||||||
|
vm->stack[vm->sp++] = result;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OP_CMP_LT: {
|
||||||
|
// Pop from stack
|
||||||
|
Value var2 = vm->stack[--vm->sp];
|
||||||
|
Value var1 = vm->stack[--vm->sp];
|
||||||
|
|
||||||
|
Value result = {0};
|
||||||
|
result.type = VAL_INT;
|
||||||
|
result.as.int_val = var1.as.int_val < var2.as.int_val;
|
||||||
|
|
||||||
|
// Push to stack
|
||||||
|
vm->stack[vm->sp++] = result;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// go to next instruction
|
||||||
|
vm->ip++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#ifndef JLANG_EVAL_H
|
#ifndef JLANG_EVAL_H
|
||||||
#define JLANG_EVAL_H
|
#define JLANG_EVAL_H
|
||||||
|
|
||||||
#include "../frontend/parser.h"
|
#include "../../frontend/parser.h"
|
||||||
#include "../memory/gc.h"
|
#include "../../memory/gc.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char *name;
|
char *name;
|
||||||
@@ -139,7 +139,7 @@ size_t eval(ASTNode *node, Environment *env, void *allocator, int debug,
|
|||||||
if (strcmp(node->data.call.name, "print") == 0) {
|
if (strcmp(node->data.call.name, "print") == 0) {
|
||||||
if (node->data.call.arg_count > 0) {
|
if (node->data.call.arg_count > 0) {
|
||||||
size_t val = eval(node->data.call.args[0], env, allocator, debug, gc);
|
size_t val = eval(node->data.call.args[0], env, allocator, debug, gc);
|
||||||
obj_print(allocator, val, "");
|
obj_print(allocator, val, "", "");
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ size_t eval(ASTNode *node, Environment *env, void *allocator, int debug,
|
|||||||
if (strcmp(node->data.call.name, "println") == 0) {
|
if (strcmp(node->data.call.name, "println") == 0) {
|
||||||
if (node->data.call.arg_count > 0) {
|
if (node->data.call.arg_count > 0) {
|
||||||
size_t val = eval(node->data.call.args[0], env, allocator, debug, gc);
|
size_t val = eval(node->data.call.args[0], env, allocator, debug, gc);
|
||||||
obj_print(allocator, val, "");
|
obj_print(allocator, val, "", "");
|
||||||
printf("\n");
|
printf("\n");
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
@@ -335,6 +335,9 @@ void ast_print(ASTNode *node, const char *prefix, int is_last) {
|
|||||||
ast_print(node->data.while_loop.cond, new_prefix, 0);
|
ast_print(node->data.while_loop.cond, new_prefix, 0);
|
||||||
ast_print(node->data.while_loop.body, new_prefix, 1);
|
ast_print(node->data.while_loop.body, new_prefix, 1);
|
||||||
break;
|
break;
|
||||||
|
case NODE_NOP:
|
||||||
|
printf("NODE_NOOP\n");
|
||||||
|
break;
|
||||||
|
|
||||||
case NODE_CALL:
|
case NODE_CALL:
|
||||||
printf("NODE_CALL(\"%s\")\n", node->data.call.name);
|
printf("NODE_CALL(\"%s\")\n", node->data.call.name);
|
||||||
|
|||||||
33
src/main.c
33
src/main.c
@@ -1,15 +1,17 @@
|
|||||||
#include "vm/eval.h"
|
#include "backend/eval/eval.h"
|
||||||
|
#include "backend/bytecode/compiler.h"
|
||||||
|
#include "backend/bytecode/vm.h"
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
if (argc != 2) {
|
if (argc != 3) {
|
||||||
printf("usage: %s <path to .j file>\n", argv[0]);
|
printf("usage: %s eval|vm|asm <path to .j file>\n", argv[0]);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
// Creamos un allocator
|
// Creamos un allocator
|
||||||
JLANG_memory_allocator *allocPtr = JLANG_CreateAllocator();
|
JLANG_memory_allocator *allocPtr = JLANG_CreateAllocator();
|
||||||
|
|
||||||
// Read file from argv
|
// Read file from argv
|
||||||
FILE *fptr = fopen(argv[1], "r");
|
FILE *fptr = fopen(argv[2], "r");
|
||||||
if (fptr == NULL) {
|
if (fptr == NULL) {
|
||||||
printf("error leyendo: %s\n", argv[1]);
|
printf("error leyendo: %s\n", argv[1]);
|
||||||
exit(1);
|
exit(1);
|
||||||
@@ -33,11 +35,26 @@ int main(int argc, char **argv) {
|
|||||||
ASTNode *block = parse(tokens, totalTokens);
|
ASTNode *block = parse(tokens, totalTokens);
|
||||||
ast_debug(block);
|
ast_debug(block);
|
||||||
|
|
||||||
Environment env = {0};
|
if (strcmp(argv[1], "eval") == 0) {
|
||||||
eval(block, &env, allocPtr, 0, 1);
|
Environment env = {0};
|
||||||
|
eval(block, &env, allocPtr, 0, 1);
|
||||||
|
|
||||||
|
// printf("\nheapSize=%zu\n", allocPtr->size);
|
||||||
|
// JLANG_visualize(allocPtr);
|
||||||
|
} else if (strcmp(argv[1], "vm") == 0){
|
||||||
|
Chunk* chunk = compile(block);
|
||||||
|
VM vm = {0};
|
||||||
|
vm.chunk = chunk;
|
||||||
|
vm.allocator = allocPtr;
|
||||||
|
print_chunk(chunk);
|
||||||
|
run_vm(&vm);
|
||||||
|
|
||||||
|
// printf("\n");
|
||||||
|
// JLANG_visualize(allocPtr);
|
||||||
|
} else {
|
||||||
|
printf("panic: WIP\n");
|
||||||
|
}
|
||||||
|
|
||||||
printf("heapSize=%zu\n", allocPtr->size);
|
|
||||||
JLANG_visualize(allocPtr);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,7 @@ void obj_free(void *allocator, size_t offset) {
|
|||||||
JLANG_free(allocator, offset);
|
JLANG_free(allocator, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void obj_print(void *allocator, size_t offset, const char *preffix) {
|
void obj_print(void *allocator, size_t offset, const char *preffix, const char *suffix) {
|
||||||
Object *obj = (Object *)JLANG_RESOLVE(allocator, offset);
|
Object *obj = (Object *)JLANG_RESOLVE(allocator, offset);
|
||||||
|
|
||||||
switch (obj->type) {
|
switch (obj->type) {
|
||||||
@@ -117,7 +117,7 @@ void obj_print(void *allocator, size_t offset, const char *preffix) {
|
|||||||
if (items[i] == offset) {
|
if (items[i] == offset) {
|
||||||
printf("<self:0x%zu>", offset);
|
printf("<self:0x%zu>", offset);
|
||||||
} else {
|
} else {
|
||||||
obj_print(allocator, items[i], "\"");
|
obj_print(allocator, items[i], "\"", "\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i < obj->data.list_val.capacity - 1) {
|
if (i < obj->data.list_val.capacity - 1) {
|
||||||
@@ -133,8 +133,8 @@ void obj_print(void *allocator, size_t offset, const char *preffix) {
|
|||||||
}
|
}
|
||||||
printf("%s", (char *)JLANG_RESOLVE(allocator, obj->data.string_val.chars));
|
printf("%s", (char *)JLANG_RESOLVE(allocator, obj->data.string_val.chars));
|
||||||
|
|
||||||
if (strcmp(preffix, "") != 0) {
|
if (strcmp(suffix, "") != 0) {
|
||||||
printf("%s", preffix);
|
printf("%s", suffix);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
Reference in New Issue
Block a user