Complete VM parity with eval and fix operator precedence

VM: add string concatenation in OP_ADD, len() built-in, multi-arg
print/println, undefined variable detection, and GC via OP_NOP.
Parser: fix operator precedence by splitting into parse_expr (+,-)
and parse_term (*,/) so 8 + 2 * 4 = 16 instead of 40.
Compiler: emit OP_NOP at start of NODE_BLOCK to trigger GC.
This commit is contained in:
Jose Luis Montañes Ojados
2026-02-18 02:26:44 +01:00
parent 4442886afa
commit da9bb6ca62
5 changed files with 129 additions and 39 deletions

View File

@@ -1,6 +1,2 @@
x = 0 x = 8 + 2 * 4
while x < 10: print(x, end="\n")
x = x + 1
if x > 1000000:
println("OK")

View File

@@ -1,4 +1,5 @@
x = 0 x = 0
while x < 1000000000: while x < 10000000:
x = x + 1 x = x + 1
println(x) print(x)
debugHeap()

View File

@@ -84,6 +84,9 @@ int compile_node(Chunk *chunk, ASTNode* node) {
} }
case NODE_BLOCK: { case NODE_BLOCK: {
int n = node->data.block.count; int n = node->data.block.count;
// NOP for gc
emit(chunk, make_instruction(OP_NOP));
for (int i=0; i<n; i++){ for (int i=0; i<n; i++){
compile_node(chunk, node->data.block.stmts[i]); compile_node(chunk, node->data.block.stmts[i]);
@@ -156,6 +159,7 @@ int compile_node(Chunk *chunk, ASTNode* node) {
chunk->code[jump_offset].operand.jump_target = chunk->code_count; chunk->code[jump_offset].operand.jump_target = chunk->code_count;
break; break;
} }
default: default:
break; break;
} }

View File

@@ -5,7 +5,6 @@
#include "compiler.h" #include "compiler.h"
#include "value.h" #include "value.h"
typedef struct { typedef struct {
Chunk *chunk; Chunk *chunk;
int ip; // instruction pointer int ip; // instruction pointer
@@ -77,6 +76,10 @@ void run_vm(VM *vm) {
case OP_LOAD_VAR: { case OP_LOAD_VAR: {
// get from vm->var // get from vm->var
int idx = instr.operand.var_index; int idx = instr.operand.var_index;
if (!vm->var_set[idx]) {
printf("error: variable '%s' no definida\n", vm->chunk->names[idx]);
return;
}
Value v = vm->vars[idx]; Value v = vm->vars[idx];
// push to stack // push to stack
vm->stack[vm->sp++] = v; vm->stack[vm->sp++] = v;
@@ -87,35 +90,49 @@ void run_vm(VM *vm) {
int nameIdx = instr.operand.call.name_index; int nameIdx = instr.operand.call.name_index;
char *name = vm->chunk->names[nameIdx]; char *name = vm->chunk->names[nameIdx];
if (strcmp(name, "print") == 0) { if (strcmp(name, "print") == 0 || strcmp(name, "println") == 0) {
Value v = vm->stack[--vm->sp];
switch (v.type) { int nParams = instr.operand.call.arg_count;
case VAL_INT: for (int i = 0; i < nParams; i++) {
printf("%d", v.as.int_val); Value v = vm->stack[vm->sp - nParams + i];
break; switch (v.type) {
case VAL_OBJ: { case VAL_INT:
// Get object from heap printf("%d", v.as.int_val);
obj_print(vm->allocator, v.as.heap_offset, "", ""); break;
case VAL_OBJ: {
// Get object from heap
obj_print(vm->allocator, v.as.heap_offset, "", "");
break;
}
default:
break;
}
} }
default: vm->sp -= nParams;
break;
if (strcmp(name, "println") == 0) {
printf("\n");
} }
} else if (strcmp(name, "println") == 0) {
Value v = vm->stack[--vm->sp]; } else if (strcmp(name, "debugHeap") == 0) {
switch (v.type) { printf("\n");
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); JLANG_visualize(vm->allocator);
break; break;
} else if (strcmp(name, "len") == 0) {
// pop value from stack
Value var1 = vm->stack[--vm->sp];
if (var1.type == VAL_OBJ) {
// Resolve obj
Object *obj = JLANG_RESOLVE(vm->allocator, var1.as.heap_offset);
Value result = {0};
result.type = VAL_INT;
result.as.int_val = obj->data.string_val.length;
// push to stack
vm->stack[vm->sp++] = result;
}
break;
} else { } else {
printf("error: function '%s' not found!\n", name); printf("error: function '%s' not found!\n", name);
return; return;
@@ -129,9 +146,41 @@ void run_vm(VM *vm) {
Value var2 = vm->stack[--vm->sp]; Value var2 = vm->stack[--vm->sp];
Value var1 = vm->stack[--vm->sp]; Value var1 = vm->stack[--vm->sp];
if (var1.type != var2.type) {
printf("panic: var types mismatch on OP_ADD\n");
return;
}
Value result = {0}; Value result = {0};
result.type = VAL_INT;
result.as.int_val = var1.as.int_val + var2.as.int_val; if (var1.type == VAL_INT) {
result.type = VAL_INT;
result.as.int_val = var1.as.int_val + var2.as.int_val;
} else if (var1.type == VAL_OBJ) {
// resolve obj
Object *obj1 = JLANG_RESOLVE(vm->allocator, var1.as.heap_offset);
Object *obj2 = JLANG_RESOLVE(vm->allocator, var2.as.heap_offset);
// get chars
char *str1 = JLANG_RESOLVE(vm->allocator, obj1->data.string_val.chars);
char *str2 = JLANG_RESOLVE(vm->allocator, obj2->data.string_val.chars);
// tmp char buffer
size_t total =
obj1->data.string_val.length + obj2->data.string_val.length;
char *tmpBuffer = (char *)malloc(total + 1);
memcpy(tmpBuffer, str1, obj1->data.string_val.length);
memcpy(tmpBuffer + obj1->data.string_val.length, str2,
obj2->data.string_val.length);
tmpBuffer[total] = '\0';
// Create new str
size_t strHeapIndex = obj_new_string(vm->allocator, tmpBuffer);
free(tmpBuffer);
// set value
result.type = VAL_OBJ;
result.as.heap_offset = strHeapIndex;
}
// Push to stack // Push to stack
vm->stack[vm->sp++] = result; vm->stack[vm->sp++] = result;
@@ -207,7 +256,28 @@ void run_vm(VM *vm) {
vm->stack[vm->sp++] = result; vm->stack[vm->sp++] = result;
break; break;
} }
case OP_NOP: {
// Pass gc
size_t roots[512];
int root_count = 0;
for (int i = 0; i < 256; i++) {
if (vm->var_set[i] && vm->vars[i].type == VAL_OBJ) {
roots[root_count++] = vm->vars[i].as.heap_offset;
}
}
for (int i = 0; i < vm->sp; i++) {
if (vm->stack[i].type == VAL_OBJ) {
roots[root_count++] = vm->stack[i].as.heap_offset;
}
}
gc_collect(vm->allocator, roots, root_count);
break;
}
default: default:
break; break;
} }

View File

@@ -65,8 +65,11 @@ ASTNode *make_node(NodeType type) {
int pos = 0; int pos = 0;
ASTNode *parse_expr(Token *tokens); ASTNode *parse_expr(Token *tokens);
ASTNode *parse_term(Token *tokens);
ASTNode *parse_factor(Token *tokens);
ASTNode *parse_term(Token *tokens) {
ASTNode *parse_factor(Token *tokens) {
if (tokens[pos].type == TOK_INT) { if (tokens[pos].type == TOK_INT) {
ASTNode *node = make_node(NODE_INT_LIT); ASTNode *node = make_node(NODE_INT_LIT);
node->data.int_val = atoi(tokens[pos].value); node->data.int_val = atoi(tokens[pos].value);
@@ -133,7 +136,6 @@ ASTNode *parse_expr(Token *tokens) {
ASTNode *left = parse_term(tokens); ASTNode *left = parse_term(tokens);
while (tokens[pos].type == TOK_PLUS || tokens[pos].type == TOK_MINUS || while (tokens[pos].type == TOK_PLUS || tokens[pos].type == TOK_MINUS ||
tokens[pos].type == TOK_STAR || tokens[pos].type == TOK_SLASH ||
tokens[pos].type == TOK_LT || tokens[pos].type == TOK_GT) { tokens[pos].type == TOK_LT || tokens[pos].type == TOK_GT) {
char op = tokens[pos].value[0]; // +,-,*,/ char op = tokens[pos].value[0]; // +,-,*,/
pos++; pos++;
@@ -148,6 +150,23 @@ ASTNode *parse_expr(Token *tokens) {
return left; return left;
} }
ASTNode *parse_term(Token *tokens) {
ASTNode *left = parse_factor(tokens);
while (tokens[pos].type == TOK_STAR || tokens[pos].type == TOK_SLASH) {
char op = tokens[pos].value[0];
pos++;
ASTNode *right = parse_factor(tokens);
ASTNode *binop = make_node(NODE_BINOP);
binop->data.binop.op = op;
binop->data.binop.left = left;
binop->data.binop.right = right;
left = binop;
}
return left;
}
ASTNode *parse_statement(Token *tokens) { ASTNode *parse_statement(Token *tokens) {
if (tokens[pos].type == TOK_ID) { if (tokens[pos].type == TOK_ID) {
if (tokens[pos + 1].type == TOK_LPAREN) { if (tokens[pos + 1].type == TOK_LPAREN) {