From 4442886afacfaccc8fe4e9964f2ba9be9e8c7e13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Luis=20Monta=C3=B1es=20Ojados?= Date: Wed, 18 Feb 2026 01:01:22 +0100 Subject: [PATCH] Add bytecode VM backend (compile AST to bytecodes + stack-based VM) New execution mode: ./run vm 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/. --- projects/vm_simple.j | 6 + projects/while.j | 4 +- readme.md | 266 +++++++++++++++++++++++++++----- src/backend/bytecode/compiler.h | 222 ++++++++++++++++++++++++++ src/backend/bytecode/opcodes.h | 36 +++++ src/backend/bytecode/value.h | 26 ++++ src/backend/bytecode/vm.h | 220 ++++++++++++++++++++++++++ src/{vm => backend/eval}/eval.h | 8 +- src/frontend/parser.h | 3 + src/main.c | 35 +++-- src/objects/object.h | 8 +- 11 files changed, 776 insertions(+), 58 deletions(-) create mode 100644 projects/vm_simple.j create mode 100644 src/backend/bytecode/compiler.h create mode 100644 src/backend/bytecode/opcodes.h create mode 100644 src/backend/bytecode/value.h create mode 100644 src/backend/bytecode/vm.h rename src/{vm => backend/eval}/eval.h (97%) diff --git a/projects/vm_simple.j b/projects/vm_simple.j new file mode 100644 index 0000000..8c29f72 --- /dev/null +++ b/projects/vm_simple.j @@ -0,0 +1,6 @@ +x = 0 +while x < 10: + x = x + 1 + +if x > 1000000: + println("OK") diff --git a/projects/while.j b/projects/while.j index 2888062..6dc4169 100644 --- a/projects/while.j +++ b/projects/while.j @@ -1,4 +1,4 @@ x = 0 -while x < 10000000: +while x < 1000000000: x = x + 1 -print x +println(x) diff --git a/readme.md b/readme.md index 66587ce..06545fe 100644 --- a/readme.md +++ b/readme.md @@ -1,52 +1,240 @@ # 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 -Esta ruta va desde lo más bajo (la memoria) hasta lo más alto (ejecutar código). +Las 5 fases del interprete estan implementadas y funcionando: -### 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: -- [ ] Terminar CMA_malloc con la cabecera compactada (Size + Marked + InUse). -- [ ] Implementar una función CMA_free que pueda liberar un bloque específico. +- **Variables y asignacion:** `x = 10` +- **Aritmetica:** `+`, `-`, `*`, `/` con enteros +- **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) 📦 -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. +### Ejemplo -Tareas clave: -- [ ] Crear un enum para los tipos (ENTERO, STRING, LISTA). -- [ ] Definir el struct Object genérico que envuelve tus datos. +``` +x = 0 +while x < 10: + x = x + 1 -### 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. +if x > 9: + println("fin") +``` ## Estructura del proyecto -- vm: maquina virtual de j-lang -- projects: carpeta con scripts en j-lang \ No newline at end of file +``` +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. diff --git a/src/backend/bytecode/compiler.h b/src/backend/bytecode/compiler.h new file mode 100644 index 0000000..697b620 --- /dev/null +++ b/src/backend/bytecode/compiler.h @@ -0,0 +1,222 @@ +#ifndef JLANG_COMPILER_H +#define JLANG_COMPILER_H + +#include "opcodes.h" +#include +#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;iconst_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;iname_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; idata.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; idata.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 \ No newline at end of file diff --git a/src/backend/bytecode/opcodes.h b/src/backend/bytecode/opcodes.h new file mode 100644 index 0000000..22efe42 --- /dev/null +++ b/src/backend/bytecode/opcodes.h @@ -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 \ No newline at end of file diff --git a/src/backend/bytecode/value.h b/src/backend/bytecode/value.h new file mode 100644 index 0000000..536c3ba --- /dev/null +++ b/src/backend/bytecode/value.h @@ -0,0 +1,26 @@ +#ifndef JLANG_VALUE_H +#define JLANG_VALUE_H + +#include "opcodes.h" +#include + +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 \ No newline at end of file diff --git a/src/backend/bytecode/vm.h b/src/backend/bytecode/vm.h new file mode 100644 index 0000000..a0a7808 --- /dev/null +++ b/src/backend/bytecode/vm.h @@ -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 \ No newline at end of file diff --git a/src/vm/eval.h b/src/backend/eval/eval.h similarity index 97% rename from src/vm/eval.h rename to src/backend/eval/eval.h index 0c0fb50..08e1eb7 100644 --- a/src/vm/eval.h +++ b/src/backend/eval/eval.h @@ -1,8 +1,8 @@ #ifndef JLANG_EVAL_H #define JLANG_EVAL_H -#include "../frontend/parser.h" -#include "../memory/gc.h" +#include "../../frontend/parser.h" +#include "../../memory/gc.h" typedef struct { 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 (node->data.call.arg_count > 0) { size_t val = eval(node->data.call.args[0], env, allocator, debug, gc); - obj_print(allocator, val, ""); + obj_print(allocator, 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 (node->data.call.arg_count > 0) { size_t val = eval(node->data.call.args[0], env, allocator, debug, gc); - obj_print(allocator, val, ""); + obj_print(allocator, val, "", ""); printf("\n"); return val; } diff --git a/src/frontend/parser.h b/src/frontend/parser.h index 7e5a938..2dd2812 100644 --- a/src/frontend/parser.h +++ b/src/frontend/parser.h @@ -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.body, new_prefix, 1); break; + case NODE_NOP: + printf("NODE_NOOP\n"); + break; case NODE_CALL: printf("NODE_CALL(\"%s\")\n", node->data.call.name); diff --git a/src/main.c b/src/main.c index 9e74372..31025e5 100644 --- a/src/main.c +++ b/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) { - if (argc != 2) { - printf("usage: %s \n", argv[0]); + if (argc != 3) { + printf("usage: %s eval|vm|asm \n", argv[0]); exit(1); } // Creamos un allocator JLANG_memory_allocator *allocPtr = JLANG_CreateAllocator(); // Read file from argv - FILE *fptr = fopen(argv[1], "r"); + FILE *fptr = fopen(argv[2], "r"); if (fptr == NULL) { printf("error leyendo: %s\n", argv[1]); exit(1); @@ -32,12 +34,27 @@ int main(int argc, char **argv) { printf("totalTokens=%d\n", totalTokens); ASTNode *block = parse(tokens, totalTokens); ast_debug(block); - - Environment env = {0}; - eval(block, &env, allocPtr, 0, 1); + + if (strcmp(argv[1], "eval") == 0) { + 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; } \ No newline at end of file diff --git a/src/objects/object.h b/src/objects/object.h index 866f735..ab24f10 100644 --- a/src/objects/object.h +++ b/src/objects/object.h @@ -94,7 +94,7 @@ void obj_free(void *allocator, size_t 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); switch (obj->type) { @@ -117,7 +117,7 @@ void obj_print(void *allocator, size_t offset, const char *preffix) { if (items[i] == offset) { printf("", offset); } else { - obj_print(allocator, items[i], "\""); + obj_print(allocator, items[i], "\"", "\""); } 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)); - if (strcmp(preffix, "") != 0) { - printf("%s", preffix); + if (strcmp(suffix, "") != 0) { + printf("%s", suffix); } break; default: