diff --git a/examples/arrays.fun b/examples/arrays.fun new file mode 100644 index 0000000..5986119 --- /dev/null +++ b/examples/arrays.fun @@ -0,0 +1,47 @@ +// Arrays basics +arr = [1, 2, 3] +print(arr) // -> [1, 2, 3] +print(arr[0] + arr[1]) // -> 3 + +// mutate last element +arr[2] = arr[2] + 10 +print(arr) // -> [1, 2, 13] + +// Sum elements with while (fixed length) +number i = 0 +number sum = 0 +number len = 3 +while i < len + sum = sum + arr[i] + i = i + 1 +print(sum) // -> 16 + +// Fill with squares using for in range +arr = [0, 0, 0, 0, 0] +for i in range(0, 5) + arr[i] = i * i +print(arr) // -> [0, 1, 4, 9, 16] + +// Nested arrays (matrix) +matrix = [[1, 2], [3, 4], [5, 6]] +print(matrix) // -> [[1, 2], [3, 4], [5, 6]] +print(matrix[1][0]) // -> 3 + +// Function that mutates an array in place (first 3 elements) +fun scale3(a, s) + number i = 0 + while i < 3 + a[i] = a[i] * s + i = i + 1 + +arr = [2, 4, 6] +scale3(arr, 5) +print(arr) // -> [10, 20, 30] + +// Function that returns an array +fun pair(x, y) + return [x, y] + +p = pair(7, 9) +print(p) // -> [7, 9] +print(p[1]) // -> 9 diff --git a/src/bytecode.c b/src/bytecode.c index 062604b..6156c57 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -72,6 +72,9 @@ static const char *opcode_name(OpCode op) { case OP_NOT: return "NOT"; case OP_DUP: return "DUP"; case OP_SWAP: return "SWAP"; + case OP_MAKE_ARRAY: return "MAKE_ARRAY"; + case OP_INDEX_GET: return "INDEX_GET"; + case OP_INDEX_SET: return "INDEX_SET"; default: return "???"; } } diff --git a/src/bytecode.h b/src/bytecode.h index 3e705cc..8424e3c 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -44,6 +44,11 @@ typedef enum { OP_DUP, // duplicate top of stack OP_SWAP, // swap top two stack values + + // 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 } OpCode; typedef struct { diff --git a/src/parser.c b/src/parser.c index d1b4be0..4af4ff9 100644 --- a/src/parser.c +++ b/src/parser.c @@ -267,6 +267,18 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) parser_fail(*pos, "Expected ')'"); return 0; } + /* postfix indexing */ + 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; + } + break; + } return 1; } @@ -276,6 +288,56 @@ 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 */ + 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; + } + break; + } + return 1; + } + + /* array literal: [expr, expr, ...] */ + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] == '[') { + (*pos)++; /* '[' */ + int count = 0; + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] != ']') { + for (;;) { + if (!emit_expression(bc, src, len, pos)) { + parser_fail(*pos, "Expected expression in array literal"); + return 0; + } + count++; + skip_spaces(src, len, pos); + if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); continue; } + break; + } + } + if (!consume_char(src, len, pos, ']')) { + parser_fail(*pos, "Expected ']' to close array literal"); + return 0; + } + bytecode_add_instruction(bc, OP_MAKE_ARRAY, count); + /* postfix indexing */ + 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; + } + break; + } return 1; } @@ -286,6 +348,18 @@ 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 */ + 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; + } + break; + } return 1; } *pos = save; @@ -338,6 +412,18 @@ 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 */ + 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; + } + break; + } free(name); return 1; } else { @@ -347,6 +433,18 @@ 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 */ + 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; + } + break; + } free(name); return 1; } @@ -798,8 +896,48 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si /* assignment or simple call */ int lidx = local_find(name); int gi = (lidx < 0) ? sym_index(name) : -1; - free(name); skip_spaces(src, len, &local_pos); + + /* array element assignment: name[expr] = expr */ + if (local_pos < len && src[local_pos] == '[') { + /* load array variable */ + if (lidx >= 0) { + bytecode_add_instruction(bc, OP_LOAD_LOCAL, lidx); + } else { + bytecode_add_instruction(bc, OP_LOAD_GLOBAL, gi); + } + local_pos++; /* '[' */ + if (!emit_expression(bc, src, len, &local_pos)) { + parser_fail(local_pos, "Expected index expression after '['"); + free(name); + return; + } + if (!consume_char(src, len, &local_pos, ']')) { + parser_fail(local_pos, "Expected ']' after index"); + free(name); + return; + } + skip_spaces(src, len, &local_pos); + if (local_pos >= len || src[local_pos] != '=') { + parser_fail(local_pos, "Expected '=' after array index"); + free(name); + return; + } + local_pos++; /* '=' */ + if (!emit_expression(bc, src, len, &local_pos)) { + parser_fail(local_pos, "Expected expression after '='"); + free(name); + return; + } + /* perform set */ + bytecode_add_instruction(bc, OP_INDEX_SET, 0); + free(name); + *pos = local_pos; + skip_to_eol(src, len, pos); + return; + } + + free(name); if (local_pos < len && src[local_pos] == '=') { local_pos++; /* '=' */ if (emit_expression(bc, src, len, &local_pos)) { diff --git a/src/value.c b/src/value.c index f1d03cb..b0a0b5a 100644 --- a/src/value.c +++ b/src/value.c @@ -3,6 +3,12 @@ #include #include +typedef struct Array { + int refcount; + int count; + Value *items; /* owns items; each item owned by array */ +} Array; + Value make_int(int64_t v) { Value val; val.type = VAL_INT; @@ -31,6 +37,58 @@ Value make_nil(void) { return v; } +Value make_array_from_values(const Value *vals, int count) { + if (count < 0) count = 0; + Array *arr = (Array*)malloc(sizeof(Array)); + if (!arr) { + Value nil = make_nil(); + return nil; + } + arr->refcount = 1; + arr->count = count; + if (count > 0) { + arr->items = (Value*)malloc(sizeof(Value) * count); + if (!arr->items) { + free(arr); + Value nil = make_nil(); + return nil; + } + for (int i = 0; i < count; ++i) { + arr->items[i] = copy_value(&vals[i]); + } + } else { + arr->items = NULL; + } + Value v; + v.type = VAL_ARRAY; + v.arr = (struct Array*)arr; + return v; +} + +int array_length(const Value *v) { + if (!v || v->type != VAL_ARRAY || !v->arr) return -1; + const Array *a = (const Array*)v->arr; + return a->count; +} + +int array_get_copy(const Value *v, int index, Value *out) { + if (!v || v->type != VAL_ARRAY || !v->arr) return 0; + const Array *a = (const Array*)v->arr; + if (index < 0 || index >= a->count) return 0; + if (out) *out = copy_value(&a->items[index]); + return 1; +} + +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; + return 1; +} + Value copy_value(const Value *v) { Value out; out.type = v->type; @@ -44,6 +102,12 @@ Value copy_value(const Value *v) { case VAL_FUNCTION: out.fn = v->fn; /* shallow copy pointer */ break; + case VAL_ARRAY: { + Array *a = (Array*)v->arr; + out.arr = (struct Array*)a; + if (a) a->refcount++; + break; + } case VAL_NIL: default: break; @@ -54,6 +118,15 @@ Value copy_value(const Value *v) { void free_value(Value v) { if (v.type == VAL_STRING && v.s) { free(v.s); + } else if (v.type == VAL_ARRAY && v.arr) { + Array *a = (Array*)v.arr; + if (--a->refcount == 0) { + for (int i = 0; i < a->count; ++i) { + free_value(a->items[i]); + } + free(a->items); + free(a); + } } /* VAL_FUNCTION: we *do not* free the Bytecode here (caller frees it) */ } @@ -69,6 +142,18 @@ void print_value(const Value *v) { case VAL_FUNCTION: printf("", (void*)v->fn); break; + case VAL_ARRAY: { + const Array *a = (const Array*)v->arr; + printf("["); + if (a) { + for (int i = 0; i < a->count; ++i) { + if (i > 0) printf(", "); + print_value(&a->items[i]); + } + } + printf("]"); + break; + } case VAL_NIL: default: printf("nil"); @@ -84,6 +169,10 @@ int value_is_truthy(const Value *v) { return v->s && v->s[0] != '\0'; case VAL_FUNCTION: return 1; + case VAL_ARRAY: { + const Array *a = (const Array*)v->arr; + return a && a->count > 0; + } case VAL_NIL: default: return 0; @@ -106,6 +195,12 @@ char *value_to_string_alloc(const Value *v) { snprintf(buf, sizeof(buf), "", (void*)v->fn); return strdup(buf); } + case VAL_ARRAY: { + int n = array_length(v); + if (n < 0) n = 0; + snprintf(buf, sizeof(buf), "[array n=%d]", n); + return strdup(buf); + } case VAL_NIL: default: return strdup("nil"); diff --git a/src/value.h b/src/value.h index 70fb955..eefb878 100644 --- a/src/value.h +++ b/src/value.h @@ -4,11 +4,13 @@ #include struct Bytecode; /* forward */ +struct Array; /* forward */ typedef enum { VAL_INT, VAL_STRING, VAL_FUNCTION, + VAL_ARRAY, VAL_NIL } ValueType; @@ -18,6 +20,7 @@ typedef struct { int64_t i; char *s; struct Bytecode *fn; + struct Array *arr; }; } Value; @@ -27,9 +30,15 @@ Value make_string(const char *s); Value make_function(struct Bytecode *fn); Value make_nil(void); -/* copy (deep for strings), free (free string only) */ -Value copy_value(const Value *v); -void free_value(Value v); +/* arrays */ +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 */ + +/* copy/free */ +Value copy_value(const Value *v); /* deep for strings, RC for arrays, shallow fn */ +void free_value(Value v); /* frees owned resources */ /* utilities */ void print_value(const Value *v); diff --git a/src/vm.c b/src/vm.c index 0cc98d0..c48b0ad 100644 --- a/src/vm.c +++ b/src/vm.c @@ -485,6 +485,72 @@ void vm_run(VM *vm, Bytecode *entry) { break; } + case OP_MAKE_ARRAY: { + int n = inst.operand; + if (n < 0 || vm->sp + 1 < n) { + fprintf(stderr, "Runtime error: invalid element count for MAKE_ARRAY\n"); + exit(1); + } + /* pop n values into temp array preserving original order */ + Value *vals = (Value*)malloc(sizeof(Value) * n); + if (!vals) { fprintf(stderr, "Runtime error: OOM in MAKE_ARRAY\n"); exit(1); } + for (int i = n - 1; i >= 0; --i) { + vals[i] = pop_value(vm); /* take ownership */ + } + /* build array by copying values, then free originals */ + Value arr = make_array_from_values(vals, n); + for (int i = 0; i < n; ++i) { + free_value(vals[i]); + } + free(vals); + push_value(vm, arr); + break; + } + + case OP_INDEX_GET: { + Value idx = pop_value(vm); + Value arr = pop_value(vm); + if (arr.type != VAL_ARRAY) { + fprintf(stderr, "Runtime type error: INDEX_GET expects array\n"); + exit(1); + } + if (idx.type != VAL_INT) { + fprintf(stderr, "Runtime type error: INDEX_GET index must be int\n"); + exit(1); + } + Value elem; + if (!array_get_copy(&arr, (int)idx.i, &elem)) { + fprintf(stderr, "Runtime error: index out of range\n"); + exit(1); + } + free_value(arr); + free_value(idx); + push_value(vm, elem); + break; + } + + case OP_INDEX_SET: { + Value v = pop_value(vm); + Value idx = pop_value(vm); + Value arr = pop_value(vm); + if (arr.type != VAL_ARRAY) { + fprintf(stderr, "Runtime type error: INDEX_SET expects array\n"); + exit(1); + } + if (idx.type != VAL_INT) { + fprintf(stderr, "Runtime type error: INDEX_SET index must be int\n"); + exit(1); + } + if (!array_set(&arr, (int)idx.i, v)) { + fprintf(stderr, "Runtime error: index out of range\n"); + exit(1); + } + /* arr modified in place; do not free v (ownership moved) */ + free_value(arr); + free_value(idx); + 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 c5e34b6..921f44f 100644 --- a/src/vm.h +++ b/src/vm.h @@ -14,7 +14,8 @@ static const char *opcode_names[] = { "LOAD_GLOBAL","STORE_GLOBAL","ADD","SUB","MUL","DIV", "LT","LTE","GT","GTE","EQ","NEQ","POP","JUMP", "JUMP_IF_FALSE","CALL","RETURN","PRINT","HALT", - "MOD","AND","OR","NOT","DUP","SWAP" + "MOD","AND","OR","NOT","DUP","SWAP", + "MAKE_ARRAY","INDEX_GET","INDEX_SET" }; typedef struct { @@ -55,7 +56,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_SWAP; // all current opcodes + return op >= OP_NOP && op <= OP_INDEX_SET; // all current opcodes } #endif