More advanced array functionality.
This commit is contained in:
parent
af16311ed5
commit
66191b1642
9 changed files with 532 additions and 38 deletions
0
examples/arrays.fun
Normal file → Executable file
0
examples/arrays.fun
Normal file → Executable file
42
examples/arrays_advanced.fun
Executable file
42
examples/arrays_advanced.fun
Executable 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]
|
||||
|
|
@ -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 "???";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
219
src/parser.c
219
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;
|
||||
}
|
||||
|
|
|
|||
142
src/value.c
142
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);
|
||||
|
|
|
|||
|
|
@ -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
137
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) {
|
||||
|
|
|
|||
5
src/vm.h
5
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue