diff --git a/examples/arrays.fun b/examples/arrays.fun old mode 100644 new mode 100755 diff --git a/examples/arrays_advanced.fun b/examples/arrays_advanced.fun new file mode 100755 index 0000000..f48a717 --- /dev/null +++ b/examples/arrays_advanced.fun @@ -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] diff --git a/src/bytecode.c b/src/bytecode.c index 6156c57..f8168d5 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -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 "???"; } } diff --git a/src/bytecode.h b/src/bytecode.h index 8424e3c..27bbd28 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -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 { diff --git a/src/parser.c b/src/parser.c index 4af4ff9..80263e0 100644 --- a/src/parser.c +++ b/src/parser.c @@ -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; } diff --git a/src/value.c b/src/value.c index b0a0b5a..70616ac 100644 --- a/src/value.c +++ b/src/value.c @@ -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); diff --git a/src/value.h b/src/value.h index eefb878..1a182c9 100644 --- a/src/value.h +++ b/src/value.h @@ -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 */ diff --git a/src/vm.c b/src/vm.c index c48b0ad..e77dab6 100644 --- a/src/vm.c +++ b/src/vm.c @@ -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) { diff --git a/src/vm.h b/src/vm.h index 921f44f..3e5f355 100644 --- a/src/vm.h +++ b/src/vm.h @@ -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