1
0
Fork 0
forked from fun/fun

More advanced array functionality.

This commit is contained in:
Johannes Findeisen 2025-09-15 01:53:41 +02:00
commit 66191b1642
9 changed files with 532 additions and 38 deletions

0
examples/arrays.fun Normal file → Executable file
View file

42
examples/arrays_advanced.fun Executable file
View file

@ -0,0 +1,42 @@
// Advanced Arrays Demo
// Start with a basic array
arr = [1, 2, 3]
print(arr) // -> [1, 2, 3]
// length (also works on strings)
print(len(arr)) // -> 3
print(len("hello")) // -> 5
// push returns new length and mutates the array
print(push(arr, 99)) // -> 4
print(arr) // -> [1, 2, 3, 99]
// pop returns the removed element and mutates the array
last = pop(arr)
print(last) // -> 99
print(arr) // -> [1, 2, 3]
// set(array, index, value) sets in place and returns the value
print(set(arr, 1, 42)) // -> 42
print(arr) // -> [1, 42, 3]
// insert(array, index, value) inserts before index; returns new length
print(insert(arr, 1, 7)) // -> 4
print(arr) // -> [1, 7, 42, 3]
// remove(array, index) removes at index and returns the element
removed = remove(arr, 2)
print(removed) // -> 42
print(arr) // -> [1, 7, 3]
// slicing: a[i:j] (end is exclusive), a[i:-1] means to the end
s1 = arr[1:3]
print(s1) // -> [7, 3]
s2 = arr[0:-1]
print(s2) // -> [1, 7, 3]
// concatenation with +
tail = [10, 20]
combined = arr + tail
print(combined) // -> [1, 7, 3, 10, 20]

View file

@ -75,6 +75,13 @@ static const char *opcode_name(OpCode op) {
case OP_MAKE_ARRAY: return "MAKE_ARRAY";
case OP_INDEX_GET: return "INDEX_GET";
case OP_INDEX_SET: return "INDEX_SET";
case OP_LEN: return "LEN";
case OP_ARR_PUSH: return "ARR_PUSH";
case OP_ARR_POP: return "ARR_POP";
case OP_ARR_SET: return "ARR_SET";
case OP_ARR_INSERT: return "ARR_INSERT";
case OP_ARR_REMOVE: return "ARR_REMOVE";
case OP_SLICE: return "SLICE";
default: return "???";
}
}

View file

@ -48,7 +48,16 @@ typedef enum {
// arrays
OP_MAKE_ARRAY, // operand = element count; pops N values, pushes array
OP_INDEX_GET, // pops index, array; pushes element copy
OP_INDEX_SET // pops value, index, array; sets and pushes nothing
OP_INDEX_SET, // pops value, index, array; sets in place
// array and builtin helpers
OP_LEN, // pops array or string; pushes length
OP_ARR_PUSH, // pops value, array; pushes new length
OP_ARR_POP, // pops array; pushes removed element
OP_ARR_SET, // pops value, index, array; pushes value
OP_ARR_INSERT, // pops value, index, array; pushes new length
OP_ARR_REMOVE, // pops index, array; pushes removed element
OP_SLICE // pops end, start, array; pushes new array
} OpCode;
typedef struct {

View file

@ -267,15 +267,30 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
parser_fail(*pos, "Expected ')'");
return 0;
}
/* postfix indexing */
/* postfix indexing or slice */
for (;;) {
skip_spaces(src, len, pos);
if (*pos < len && src[*pos] == '[') {
(*pos)++;
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected index expression"); return 0; }
if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after index"); return 0; }
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
continue;
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected start expression"); return 0; }
skip_spaces(src, len, pos);
if (*pos < len && src[*pos] == ':') {
(*pos)++;
skip_spaces(src, len, pos);
size_t savep4 = *pos;
if (!emit_expression(bc, src, len, pos)) {
*pos = savep4;
int ci4 = bytecode_add_constant(bc, make_int(-1));
bytecode_add_instruction(bc, OP_LOAD_CONST, ci4);
}
if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after slice"); return 0; }
bytecode_add_instruction(bc, OP_SLICE, 0);
continue;
} else {
if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after index"); return 0; }
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
continue;
}
}
break;
}
@ -288,15 +303,34 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
int ci = bytecode_add_constant(bc, make_string(s));
free(s);
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
/* postfix indexing */
/* postfix indexing or slice */
for (;;) {
skip_spaces(src, len, pos);
if (*pos < len && src[*pos] == '[') {
(*pos)++;
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected index expression"); return 0; }
if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after index"); return 0; }
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
continue;
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected start expression"); return 0; }
skip_spaces(src, len, pos);
if (*pos < len && src[*pos] == ':') {
(*pos)++;
skip_spaces(src, len, pos);
/* end is optional; if missing, use -1 (till end) */
int has_end = 0;
size_t savep = *pos;
if (emit_expression(bc, src, len, pos)) {
has_end = 1;
} else {
*pos = savep;
int ci = bytecode_add_constant(bc, make_int(-1));
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
}
if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after slice"); return 0; }
bytecode_add_instruction(bc, OP_SLICE, 0);
continue;
} else {
if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after index"); return 0; }
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
continue;
}
}
break;
}
@ -326,15 +360,30 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
return 0;
}
bytecode_add_instruction(bc, OP_MAKE_ARRAY, count);
/* postfix indexing */
/* postfix indexing or slice */
for (;;) {
skip_spaces(src, len, pos);
if (*pos < len && src[*pos] == '[') {
(*pos)++;
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected index expression"); return 0; }
if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after index"); return 0; }
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
continue;
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected start expression"); return 0; }
skip_spaces(src, len, pos);
if (*pos < len && src[*pos] == ':') {
(*pos)++;
skip_spaces(src, len, pos);
size_t savep3 = *pos;
if (!emit_expression(bc, src, len, pos)) {
*pos = savep3;
int ci3 = bytecode_add_constant(bc, make_int(-1));
bytecode_add_instruction(bc, OP_LOAD_CONST, ci3);
}
if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after slice"); return 0; }
bytecode_add_instruction(bc, OP_SLICE, 0);
continue;
} else {
if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after index"); return 0; }
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
continue;
}
}
break;
}
@ -348,15 +397,33 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
if (ok) {
int ci = bytecode_add_constant(bc, make_int(ival));
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
/* postfix indexing */
/* postfix indexing or slice */
for (;;) {
skip_spaces(src, len, pos);
if (*pos < len && src[*pos] == '[') {
(*pos)++;
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected index expression"); return 0; }
if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after index"); return 0; }
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
continue;
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected start expression"); return 0; }
skip_spaces(src, len, pos);
if (*pos < len && src[*pos] == ':') {
(*pos)++;
skip_spaces(src, len, pos);
int has_end = 0;
size_t savep2 = *pos;
if (emit_expression(bc, src, len, pos)) {
has_end = 1;
} else {
*pos = savep2;
int ci2 = bytecode_add_constant(bc, make_int(-1));
bytecode_add_instruction(bc, OP_LOAD_CONST, ci2);
}
if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after slice"); return 0; }
bytecode_add_instruction(bc, OP_SLICE, 0);
continue;
} else {
if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after index"); return 0; }
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
continue;
}
}
break;
}
@ -380,6 +447,68 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
int is_call = (*pos < len && src[*pos] == '(');
if (is_call) {
/* builtins */
if (strcmp(name, "len") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "len expects 1 argument"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after len arg"); free(name); return 0; }
bytecode_add_instruction(bc, OP_LEN, 0);
free(name);
return 1;
}
if (strcmp(name, "push") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "push expects array"); free(name); return 0; }
if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "push expects 2 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "push expects value"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after push args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_ARR_PUSH, 0);
free(name);
return 1;
}
if (strcmp(name, "pop") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pop expects array"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after pop arg"); free(name); return 0; }
bytecode_add_instruction(bc, OP_ARR_POP, 0);
free(name);
return 1;
}
if (strcmp(name, "set") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "set expects array"); free(name); return 0; }
if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "set expects 3 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "set expects index"); free(name); return 0; }
if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "set expects 3 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "set expects value"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after set args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_ARR_SET, 0);
free(name);
return 1;
}
if (strcmp(name, "insert") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "insert expects array"); free(name); return 0; }
if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "insert expects 3 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "insert expects index"); free(name); return 0; }
if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "insert expects 3 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "insert expects value"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after insert args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_ARR_INSERT, 0);
free(name);
return 1;
}
if (strcmp(name, "remove") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "remove expects array"); free(name); return 0; }
if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "remove expects 2 args"); free(name); return 0; }
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "remove expects index"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after remove args"); free(name); return 0; }
bytecode_add_instruction(bc, OP_ARR_REMOVE, 0);
free(name);
return 1;
}
/* push function value first */
if (local_idx >= 0) {
bytecode_add_instruction(bc, OP_LOAD_LOCAL, local_idx);
@ -412,15 +541,30 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
printf("compile: CALL %s with %d arg(s)\n", name, argc);
#endif
bytecode_add_instruction(bc, OP_CALL, argc);
/* postfix indexing */
/* postfix indexing or slice */
for (;;) {
skip_spaces(src, len, pos);
if (*pos < len && src[*pos] == '[') {
(*pos)++;
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected index expression"); free(name); return 0; }
if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after index"); free(name); return 0; }
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
continue;
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected start expression"); free(name); return 0; }
skip_spaces(src, len, pos);
if (*pos < len && src[*pos] == ':') {
(*pos)++;
skip_spaces(src, len, pos);
size_t svp = *pos;
if (!emit_expression(bc, src, len, pos)) {
*pos = svp;
int ci = bytecode_add_constant(bc, make_int(-1));
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
}
if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after slice"); free(name); return 0; }
bytecode_add_instruction(bc, OP_SLICE, 0);
continue;
} else {
if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after index"); free(name); return 0; }
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
continue;
}
}
break;
}
@ -433,15 +577,30 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
int gi = sym_index(name);
bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi);
}
/* postfix indexing */
/* postfix indexing or slice */
for (;;) {
skip_spaces(src, len, pos);
if (*pos < len && src[*pos] == '[') {
(*pos)++;
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected index expression"); free(name); return 0; }
if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after index"); free(name); return 0; }
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
continue;
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "Expected start expression"); free(name); return 0; }
skip_spaces(src, len, pos);
if (*pos < len && src[*pos] == ':') {
(*pos)++;
skip_spaces(src, len, pos);
size_t svp = *pos;
if (!emit_expression(bc, src, len, pos)) {
*pos = svp;
int ci = bytecode_add_constant(bc, make_int(-1));
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
}
if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after slice"); free(name); return 0; }
bytecode_add_instruction(bc, OP_SLICE, 0);
continue;
} else {
if (!consume_char(src, len, pos, ']')) { parser_fail(*pos, "Expected ']' after index"); free(name); return 0; }
bytecode_add_instruction(bc, OP_INDEX_GET, 0);
continue;
}
}
break;
}

View file

@ -83,12 +83,116 @@ int array_set(Value *v, int index, Value newElem) {
if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
Array *a = (Array*)v->arr;
if (index < 0 || index >= a->count) return 0;
/* replace element: free old, take ownership of newElem */
free_value(a->items[index]);
a->items[index] = newElem;
a->items[index] = newElem; /* take ownership */
return 1;
}
static int ensure_array_capacity(Array *a, int newCount) {
if (newCount <= a->count) return 1;
/* grow to at least newCount; double strategy */
int curr = a->count;
int cap = curr;
if (cap < 4) cap = 4;
while (cap < newCount) cap *= 2;
Value *newItems = (Value*)realloc(a->items, sizeof(Value) * cap);
if (!newItems) return 0;
/* if growing beyond current count, initialize new slots to nil */
if (cap > a->count) {
for (int i = a->count; i < cap; ++i) {
newItems[i] = make_nil();
}
}
a->items = newItems;
return 1;
}
int array_push(Value *v, Value newElem) {
if (!v || v->type != VAL_ARRAY || !v->arr) return -1;
Array *a = (Array*)v->arr;
/* ensure capacity for count+1 by reallocating items array to at least count+1 elements */
Value *newItems = (Value*)realloc(a->items, sizeof(Value) * (a->count + 1));
if (!newItems) { free_value(newElem); return -1; }
a->items = newItems;
a->items[a->count] = newElem; /* take ownership */
a->count += 1;
return a->count;
}
int array_pop(Value *v, Value *out) {
if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
Array *a = (Array*)v->arr;
if (a->count <= 0) return 0;
int idx = a->count - 1;
if (out) *out = a->items[idx]; /* transfer ownership */
else free_value(a->items[idx]);
a->count -= 1;
return 1;
}
int array_insert(Value *v, int index, Value newElem) {
if (!v || v->type != VAL_ARRAY || !v->arr) return -1;
Array *a = (Array*)v->arr;
if (index < 0) index = 0;
if (index > a->count) index = a->count;
Value *newItems = (Value*)realloc(a->items, sizeof(Value) * (a->count + 1));
if (!newItems) { free_value(newElem); return -1; }
a->items = newItems;
/* shift right */
for (int i = a->count; i > index; --i) {
a->items[i] = a->items[i - 1];
}
a->items[index] = newElem; /* take ownership */
a->count += 1;
return a->count;
}
int array_remove(Value *v, int index, Value *out) {
if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
Array *a = (Array*)v->arr;
if (index < 0 || index >= a->count) return 0;
if (out) *out = a->items[index]; /* transfer ownership */
else free_value(a->items[index]);
/* shift left */
for (int i = index; i < a->count - 1; ++i) {
a->items[i] = a->items[i + 1];
}
a->count -= 1;
return 1;
}
Value array_slice(const Value *v, int start, int end) {
if (!v || v->type != VAL_ARRAY || !v->arr) return make_nil();
const Array *a = (const Array*)v->arr;
int n = a->count;
if (start < 0) start = 0;
if (end < 0 || end > n) end = n;
if (start > end) start = end;
int m = end - start;
if (m <= 0) {
return make_array_from_values(NULL, 0);
}
return make_array_from_values(a->items + start, m);
}
Value array_concat(const Value *av, const Value *bv) {
if (!av || !bv || av->type != VAL_ARRAY || bv->type != VAL_ARRAY) return make_nil();
const Array *a = (const Array*)av->arr;
const Array *b = (const Array*)bv->arr;
int na = a ? a->count : 0;
int nb = b ? b->count : 0;
int total = na + nb;
if (total <= 0) return make_array_from_values(NULL, 0);
Value *tmp = (Value*)malloc(sizeof(Value) * total);
if (!tmp) return make_nil();
for (int i = 0; i < na; ++i) tmp[i] = a->items[i];
for (int j = 0; j < nb; ++j) tmp[na + j] = b->items[j];
Value out = make_array_from_values(tmp, total);
/* free temporaries we copied from (deep copy in make_array_from_values) */
free(tmp);
return out;
}
Value copy_value(const Value *v) {
Value out;
out.type = v->type;
@ -115,6 +219,40 @@ Value copy_value(const Value *v) {
return out;
}
/* deep copy including arrays (recursively copies items) */
Value deep_copy_value(const Value *v) {
switch (v->type) {
case VAL_INT:
return make_int(v->i);
case VAL_STRING:
return make_string(v->s ? v->s : "");
case VAL_FUNCTION:
return make_function(v->fn); /* shallow pointer for function bytecode */
case VAL_ARRAY: {
const Array *a = (const Array*)v->arr;
if (!a || a->count <= 0) {
return make_array_from_values(NULL, 0);
}
/* copy items deeply */
Value *tmp = (Value*)malloc(sizeof(Value) * a->count);
if (!tmp) return make_nil();
for (int i = 0; i < a->count; ++i) {
tmp[i] = deep_copy_value(&a->items[i]);
}
Value out = make_array_from_values(tmp, a->count);
/* free temporaries (we created new deep copies already moved into out) */
for (int i = 0; i < a->count; ++i) {
free_value(tmp[i]);
}
free(tmp);
return out;
}
case VAL_NIL:
default:
return make_nil();
}
}
void free_value(Value v) {
if (v.type == VAL_STRING && v.s) {
free(v.s);

View file

@ -35,9 +35,16 @@ Value make_array_from_values(const Value *vals, int count); /* deep-copies vals
int array_length(const Value *v); /* returns -1 if not array */
int array_get_copy(const Value *v, int index, Value *out); /* returns 0 on error; out = copy_value(item) */
int array_set(Value *v, int index, Value newElem); /* returns 0 on error; takes ownership of newElem */
int array_push(Value *v, Value newElem); /* returns new length or -1 on error */
int array_pop(Value *v, Value *out); /* returns 1 on success, out takes ownership */
int array_insert(Value *v, int index, Value newElem); /* returns new length or -1 */
int array_remove(Value *v, int index, Value *out); /* returns 1 on success */
Value array_slice(const Value *v, int start, int end); /* negative end means till end */
Value array_concat(const Value *a, const Value *b); /* returns new array */
/* copy/free */
Value copy_value(const Value *v); /* deep for strings, RC for arrays, shallow fn */
Value deep_copy_value(const Value *v); /* deep copy including arrays */
void free_value(Value v); /* frees owned resources */
/* utilities */

137
src/vm.c
View file

@ -212,8 +212,13 @@ void vm_run(VM *vm, Bytecode *entry) {
free_value(a);
free_value(b);
push_value(vm, res);
} else if (a.type == VAL_ARRAY && b.type == VAL_ARRAY) {
Value res = array_concat(&a, &b);
free_value(a);
free_value(b);
push_value(vm, res);
} else {
fprintf(stderr, "Runtime type error: ADD expects both ints or both strings, got %s and %s\n",
fprintf(stderr, "Runtime type error: ADD expects both ints, both strings, or both arrays, got %s and %s\n",
value_type_name(a.type), value_type_name(b.type));
exit(1);
}
@ -440,10 +445,13 @@ void vm_run(VM *vm, Bytecode *entry) {
case OP_PRINT: {
Value v = pop_value(vm);
/* snapshot value at print time (deep copy arrays) */
Value snap = deep_copy_value(&v);
free_value(v);
if (vm->output_count < VM_OUTPUT_SIZE) {
vm->output[vm->output_count++] = v; // store instead of printing
vm->output[vm->output_count++] = snap; // store snapshot
} else {
free_value(v); // prevent leak
free_value(snap); // prevent leak
fprintf(stderr, "Runtime error: output buffer overflow\n");
exit(1);
}
@ -551,6 +559,129 @@ void vm_run(VM *vm, Bytecode *entry) {
break;
}
case OP_LEN: {
Value a = pop_value(vm);
int len = 0;
if (a.type == VAL_STRING) {
len = (int)(a.s ? (int)strlen(a.s) : 0);
} else if (a.type == VAL_ARRAY) {
len = array_length(&a);
if (len < 0) len = 0;
} else {
fprintf(stderr, "Runtime type error: LEN expects array or string\n");
exit(1);
}
free_value(a);
push_value(vm, make_int(len));
break;
}
case OP_ARR_PUSH: {
Value v = pop_value(vm);
Value arr = pop_value(vm);
if (arr.type != VAL_ARRAY) {
fprintf(stderr, "Runtime type error: ARR_PUSH expects array\n");
exit(1);
}
int n = array_push(&arr, v);
if (n < 0) {
fprintf(stderr, "Runtime error: push failed (OOM?)\n");
exit(1);
}
free_value(arr);
push_value(vm, make_int(n));
break;
}
case OP_ARR_POP: {
Value arr = pop_value(vm);
if (arr.type != VAL_ARRAY) {
fprintf(stderr, "Runtime type error: ARR_POP expects array\n");
exit(1);
}
Value out;
if (!array_pop(&arr, &out)) {
fprintf(stderr, "Runtime error: pop from empty array\n");
exit(1);
}
free_value(arr);
push_value(vm, out);
break;
}
case OP_ARR_SET: {
Value v = pop_value(vm);
Value idx = pop_value(vm);
Value arr = pop_value(vm);
if (arr.type != VAL_ARRAY || idx.type != VAL_INT) {
fprintf(stderr, "Runtime type error: ARR_SET expects (array, int, value)\n");
exit(1);
}
if (!array_set(&arr, (int)idx.i, v)) {
fprintf(stderr, "Runtime error: set index out of range\n");
exit(1);
}
free_value(arr);
free_value(idx);
/* v already owned by array; push copy for return value */
push_value(vm, copy_value(&v));
free_value(v);
break;
}
case OP_ARR_INSERT: {
Value v = pop_value(vm);
Value idx = pop_value(vm);
Value arr = pop_value(vm);
if (arr.type != VAL_ARRAY || idx.type != VAL_INT) {
fprintf(stderr, "Runtime type error: ARR_INSERT expects (array, int, value)\n");
exit(1);
}
int n = array_insert(&arr, (int)idx.i, v);
if (n < 0) {
fprintf(stderr, "Runtime error: insert failed (OOM?)\n");
exit(1);
}
free_value(arr);
free_value(idx);
push_value(vm, make_int(n));
break;
}
case OP_ARR_REMOVE: {
Value idx = pop_value(vm);
Value arr = pop_value(vm);
if (arr.type != VAL_ARRAY || idx.type != VAL_INT) {
fprintf(stderr, "Runtime type error: ARR_REMOVE expects (array, int)\n");
exit(1);
}
Value out;
if (!array_remove(&arr, (int)idx.i, &out)) {
fprintf(stderr, "Runtime error: remove index out of range\n");
exit(1);
}
free_value(arr);
free_value(idx);
push_value(vm, out);
break;
}
case OP_SLICE: {
Value end = pop_value(vm);
Value start = pop_value(vm);
Value arr = pop_value(vm);
if (arr.type != VAL_ARRAY || start.type != VAL_INT || end.type != VAL_INT) {
fprintf(stderr, "Runtime type error: SLICE expects (array, int, int)\n");
exit(1);
}
Value out = array_slice(&arr, (int)start.i, (int)end.i);
free_value(arr);
free_value(start);
free_value(end);
push_value(vm, out);
break;
}
case OP_LOAD_GLOBAL: {
int idx = inst.operand;
if (idx < 0 || idx >= VM_MAX_GLOBALS) {

View file

@ -15,7 +15,8 @@ static const char *opcode_names[] = {
"LT","LTE","GT","GTE","EQ","NEQ","POP","JUMP",
"JUMP_IF_FALSE","CALL","RETURN","PRINT","HALT",
"MOD","AND","OR","NOT","DUP","SWAP",
"MAKE_ARRAY","INDEX_GET","INDEX_SET"
"MAKE_ARRAY","INDEX_GET","INDEX_SET",
"LEN","ARR_PUSH","ARR_POP","ARR_SET","ARR_INSERT","ARR_REMOVE","SLICE"
};
typedef struct {
@ -56,7 +57,7 @@ void vm_dump_globals(VM *vm);
void vm_run(VM *vm, Bytecode *entry);
static inline int opcode_is_valid(int op) {
return op >= OP_NOP && op <= OP_INDEX_SET; // all current opcodes
return op >= OP_NOP && op <= OP_SLICE; // all current opcodes
}
#endif