From a4f4fcba99add12415181e862ce7d43f1f8e5220 Mon Sep 17 00:00:00 2001 From: hanez Date: Mon, 15 Sep 2025 03:59:03 +0200 Subject: [PATCH] More built-ins. --- examples/builtins_extended.fun | 49 +++++++ src/bytecode.c | 16 +++ src/bytecode.h | 26 +++- src/parser.c | 147 ++++++++++++++++++++ src/value.c | 150 ++++++++++++++++++++ src/value.h | 12 ++ src/vm.c | 247 +++++++++++++++++++++++++++++++++ src/vm.h | 8 +- 8 files changed, 652 insertions(+), 3 deletions(-) create mode 100755 examples/builtins_extended.fun diff --git a/examples/builtins_extended.fun b/examples/builtins_extended.fun new file mode 100755 index 0000000..98ecb8d --- /dev/null +++ b/examples/builtins_extended.fun @@ -0,0 +1,49 @@ +#!/usr/bin/env fun + +// Built-ins: strings, array utils, iteration helpers, and math + +// Strings +print(join(split("a,b,c", ","), "-")) // -> a-b-c +print(substr("abcdef", 2, 3)) // -> cde +print(find("hello world", "wor")) // -> 6 + +// Array utils +arr = [1, 2, 3, 2] +print(contains(arr, 2)) // -> 1 +print(indexOf(arr, 3)) // -> 2 +clear(arr) +print(len(arr)) // -> 0 + +// Iteration helpers +nums = [10, 20, 30] +print(enumerate(nums)) // -> [[0, 10], [1, 20], [2, 30]] +more = [100, 200, 300, 400] +print(zip(nums, more)) // -> [[10, 100], [20, 200], [30, 300]] + +// Math +print(min(5, 9)) // -> 5 +print(max(5, 9)) // -> 9 +print(clamp(15, 0, 10)) // -> 10 +print(abs(-7)) // -> 7 +print(pow(2, 8)) // -> 256 +random(123) // seed RNG +print(randomInt(0, 3)) // -> 0..2 (deterministic for seed 123) +print(randomInt(5, 6)) // -> 5 + +/* Expected output: +a-b-c +cde +6 +1 +2 +0 +[[0, 10], [1, 20], [2, 30]] +[[10, 100], [20, 200], [30, 300]] +5 +9 +10 +7 +256 +0 +5 +*/ diff --git a/src/bytecode.c b/src/bytecode.c index 0d615e2..c480569 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -84,6 +84,22 @@ static const char *opcode_name(OpCode op) { case OP_SLICE: return "SLICE"; case OP_TO_NUMBER: return "TO_NUMBER"; case OP_TO_STRING: return "TO_STRING"; + case OP_SPLIT: return "SPLIT"; + case OP_JOIN: return "JOIN"; + case OP_SUBSTR: return "SUBSTR"; + case OP_FIND: return "FIND"; + case OP_CONTAINS: return "CONTAINS"; + case OP_INDEX_OF: return "INDEX_OF"; + case OP_CLEAR: return "CLEAR"; + case OP_ENUMERATE: return "ENUMERATE"; + case OP_ZIP: return "ZIP"; + case OP_MIN: return "MIN"; + case OP_MAX: return "MAX"; + case OP_CLAMP: return "CLAMP"; + case OP_ABS: return "ABS"; + case OP_POW: return "POW"; + case OP_RANDOM_SEED: return "RANDOM_SEED"; + case OP_RANDOM_INT: return "RANDOM_INT"; default: return "???"; } } diff --git a/src/bytecode.h b/src/bytecode.h index ce11c81..595d8b3 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -61,7 +61,31 @@ typedef enum { // conversions OP_TO_NUMBER, // pops any; pushes int (parse strings) - OP_TO_STRING // pops any; pushes string + OP_TO_STRING, // pops any; pushes string + + // string ops + OP_SPLIT, // pops sep, string; pushes array of strings + OP_JOIN, // pops sep, array; pushes string + OP_SUBSTR, // pops len, start, string; pushes string + OP_FIND, // pops needle, haystack; pushes int index or -1 + + // array utils + OP_CONTAINS, // pops value, array; pushes 1/0 + OP_INDEX_OF, // pops value, array; pushes index or -1 + OP_CLEAR, // pops array; clears it; pushes nothing (we'll push 0) + + // iteration helpers + OP_ENUMERATE, // pops array; pushes array of [index, value] + OP_ZIP, // pops b, a; pushes array of [a[i], b[i]] + + // math + OP_MIN, // pops b, a; pushes min(a,b) + OP_MAX, // pops b, a; pushes max(a,b) + OP_CLAMP, // pops hi, lo, x; pushes clamped + OP_ABS, // pops x; pushes |x| + OP_POW, // pops b, a; pushes a^b + OP_RANDOM_SEED, // pops seed; sets RNG seed; pushes nothing (we'll push 0) + OP_RANDOM_INT // pops hi, lo; pushes random int in [lo, hi) } OpCode; typedef struct { diff --git a/src/parser.c b/src/parser.c index 54fa1ba..c151021 100644 --- a/src/parser.c +++ b/src/parser.c @@ -535,6 +535,153 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) free(name); return 1; } + /* string ops */ + if (strcmp(name, "split") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "split expects string"); free(name); return 0; } + if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "split expects 2 args"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "split expects separator"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after split args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_SPLIT, 0); + free(name); + return 1; + } + if (strcmp(name, "join") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "join expects array"); free(name); return 0; } + if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "join expects 2 args"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "join expects separator"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after join args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_JOIN, 0); + free(name); + return 1; + } + if (strcmp(name, "substr") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "substr expects string"); free(name); return 0; } + if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "substr expects 3 args"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "substr expects start"); free(name); return 0; } + if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "substr expects 3 args"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "substr expects len"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after substr args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_SUBSTR, 0); + free(name); + return 1; + } + if (strcmp(name, "find") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "find expects haystack"); free(name); return 0; } + if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "find expects 2 args"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "find expects needle"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after find args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_FIND, 0); + free(name); + return 1; + } + /* array utils */ + if (strcmp(name, "contains") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "contains expects array"); free(name); return 0; } + if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "contains expects 2 args"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "contains expects value"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after contains args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_CONTAINS, 0); + free(name); + return 1; + } + if (strcmp(name, "indexOf") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "indexOf expects array"); free(name); return 0; } + if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "indexOf expects 2 args"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "indexOf expects value"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after indexOf args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_INDEX_OF, 0); + free(name); + return 1; + } + if (strcmp(name, "clear") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "clear expects array"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after clear arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_CLEAR, 0); + free(name); + return 1; + } + /* iteration helpers */ + if (strcmp(name, "enumerate") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "enumerate expects array"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after enumerate arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_ENUMERATE, 0); + free(name); + return 1; + } + if (strcmp(name, "zip") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "zip expects first array"); free(name); return 0; } + if (*pos < len && src[*pos] == ',') { (*pos)++; skip_spaces(src, len, pos); } else { parser_fail(*pos, "zip expects 2 args"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "zip expects second array"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after zip args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_ZIP, 0); + free(name); + return 1; + } + /* math */ + if (strcmp(name, "min") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "min expects 2 args"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "min expects 2 args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_MIN, 0); + free(name); + return 1; + } + if (strcmp(name, "max") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "max expects 2 args"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "max expects 2 args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_MAX, 0); + free(name); + return 1; + } + if (strcmp(name, "clamp") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "clamp expects 3 args"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "clamp expects 3 args"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "clamp expects 3 args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_CLAMP, 0); + free(name); + return 1; + } + if (strcmp(name, "abs") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "abs expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_ABS, 0); + free(name); + return 1; + } + if (strcmp(name, "pow") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "pow expects 2 args"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "pow expects 2 args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_POW, 0); + free(name); + return 1; + } + if (strcmp(name, "random") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "random expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_RANDOM_SEED, 0); + free(name); + return 1; + } + if (strcmp(name, "randomInt") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "randomInt expects 2 args"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "randomInt expects 2 args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_RANDOM_INT, 0); + free(name); + return 1; + } /* push function value first */ if (local_idx >= 0) { diff --git a/src/value.c b/src/value.c index 70616ac..e88dd0b 100644 --- a/src/value.c +++ b/src/value.c @@ -345,3 +345,153 @@ char *value_to_string_alloc(const Value *v) { } } +int value_equals(const Value *a, const Value *b) { + if (a->type != b->type) return 0; + switch (a->type) { + case VAL_INT: return a->i == b->i; + case VAL_STRING: { + const char *sa = a->s ? a->s : ""; + const char *sb = b->s ? b->s : ""; + return strcmp(sa, sb) == 0; + } + default: return 0; + } +} + +int array_contains(const Value *v, const Value *needle) { + if (!v || v->type != VAL_ARRAY || !v->arr) return 0; + const Array *a = (const Array*)v->arr; + for (int i = 0; i < a->count; ++i) { + if (value_equals(&a->items[i], needle)) return 1; + } + return 0; +} + +int array_index_of(const Value *v, const Value *needle) { + if (!v || v->type != VAL_ARRAY || !v->arr) return -1; + const Array *a = (const Array*)v->arr; + for (int i = 0; i < a->count; ++i) { + if (value_equals(&a->items[i], needle)) return i; + } + return -1; +} + +void array_clear(Value *v) { + if (!v || v->type != VAL_ARRAY || !v->arr) return; + Array *a = (Array*)v->arr; + for (int i = 0; i < a->count; ++i) { + free_value(a->items[i]); + } + a->count = 0; +} + +char *string_substr(const char *s, int start, int len) { + if (!s) return strdup(""); + int n = (int)strlen(s); + if (start < 0) start = 0; + if (start > n) start = n; + if (len < 0) len = 0; + if (start + len > n) len = n - start; + char *out = (char*)malloc((size_t)len + 1); + if (!out) return strdup(""); + memcpy(out, s + start, (size_t)len); + out[len] = '\0'; + return out; +} + +int string_find(const char *hay, const char *needle) { + if (!hay || !needle) return -1; + const char *p = strstr(hay, needle); + if (!p) return -1; + return (int)(p - hay); +} + +Value string_split_to_array(const char *s, const char *sep) { + if (!s) s = ""; + if (!sep) sep = ""; + int seplen = (int)strlen(sep); + if (seplen == 0) { + /* split into characters */ + int n = (int)strlen(s); + if (n <= 0) return make_array_from_values(NULL, 0); + Value *tmp = (Value*)malloc(sizeof(Value) * n); + if (!tmp) return make_array_from_values(NULL, 0); + for (int i = 0; i < n; ++i) { + char ch[2] = { s[i], 0 }; + tmp[i] = make_string(ch); + } + Value arr = make_array_from_values(tmp, n); + for (int i = 0; i < n; ++i) free_value(tmp[i]); + free(tmp); + return arr; + } + /* split by separator */ + Value *parts = NULL; + int count = 0; + int cap = 0; + const char *cur = s; + const char *pos = NULL; + while ((pos = strstr(cur, sep)) != NULL) { + int len = (int)(pos - cur); + char *piece = (char*)malloc((size_t)len + 1); + if (!piece) break; + memcpy(piece, cur, (size_t)len); + piece[len] = '\0'; + if (count >= cap) { + cap = cap == 0 ? 4 : cap * 2; + parts = (Value*)realloc(parts, sizeof(Value) * cap); + } + parts[count++] = make_string(piece); + free(piece); + cur = pos + seplen; + } + /* tail */ + char *tail = strdup(cur ? cur : ""); + if (count >= cap) { + cap = cap == 0 ? 1 : cap + 1; + parts = (Value*)realloc(parts, sizeof(Value) * cap); + } + parts[count++] = make_string(tail ? tail : ""); + free(tail); + + Value arr = make_array_from_values(parts, count); + for (int i = 0; i < count; ++i) free_value(parts[i]); + free(parts); + return arr; +} + +char *array_join_with_sep(const Value *v, const char *sep) { + if (!v || v->type != VAL_ARRAY || !v->arr) return strdup(""); + if (!sep) sep = ""; + const Array *a = (const Array*)v->arr; + /* compute total length */ + size_t total = 0; + int n = a ? a->count : 0; + char **parts = (char**)malloc(sizeof(char*) * (n > 0 ? n : 1)); + if (!parts) return strdup(""); + for (int i = 0; i < n; ++i) { + parts[i] = value_to_string_alloc(&a->items[i]); + total += strlen(parts[i]); + if (i + 1 < n) total += strlen(sep); + } + char *out = (char*)malloc(total + 1); + if (!out) { + for (int i = 0; i < n; ++i) free(parts[i]); + free(parts); + return strdup(""); + } + size_t off = 0; + for (int i = 0; i < n; ++i) { + size_t li = strlen(parts[i]); + memcpy(out + off, parts[i], li); off += li; + if (i + 1 < n) { + size_t ls = strlen(sep); + memcpy(out + off, sep, ls); off += ls; + } + free(parts[i]); + } + free(parts); + out[off] = '\0'; + return out; +} + diff --git a/src/value.h b/src/value.h index 1a182c9..46aaef2 100644 --- a/src/value.h +++ b/src/value.h @@ -50,9 +50,21 @@ void free_value(Value v); /* frees owned resources */ /* utilities */ void print_value(const Value *v); int value_is_truthy(const Value *v); +int value_equals(const Value *a, const Value *b); /* int/string equality */ /* stringify into a newly-allocated C string; caller must free */ char *value_to_string_alloc(const Value *v); +/* array utils */ +int array_contains(const Value *arr, const Value *needle); /* 1/0 */ +int array_index_of(const Value *arr, const Value *needle); /* idx or -1 */ +void array_clear(Value *arr); /* free elements, count=0 */ + +/* string helpers returning newly allocated C strings or arrays */ +char *string_substr(const char *s, int start, int len); /* clamps bounds */ +int string_find(const char *hay, const char *needle); /* index or -1 */ +Value string_split_to_array(const char *s, const char *sep); /* array of strings */ +char *array_join_with_sep(const Value *arr, const char *sep); /* join items as strings */ + #endif diff --git a/src/vm.c b/src/vm.c index 0395cad..63dac9e 100644 --- a/src/vm.c +++ b/src/vm.c @@ -723,6 +723,253 @@ void vm_run(VM *vm, Bytecode *entry) { break; } + case OP_SPLIT: { + Value sep = pop_value(vm); + Value str = pop_value(vm); + if (str.type != VAL_STRING || sep.type != VAL_STRING) { + fprintf(stderr, "Runtime type error: SPLIT expects (string, string)\n"); + exit(1); + } + Value arr = string_split_to_array(str.s ? str.s : "", sep.s ? sep.s : ""); + free_value(str); + free_value(sep); + push_value(vm, arr); + break; + } + + case OP_JOIN: { + Value sep = pop_value(vm); + Value arr = pop_value(vm); + if (arr.type != VAL_ARRAY || sep.type != VAL_STRING) { + fprintf(stderr, "Runtime type error: JOIN expects (array, string)\n"); + exit(1); + } + char *s = array_join_with_sep(&arr, sep.s ? sep.s : ""); + Value out = make_string(s ? s : ""); + if (s) free(s); + free_value(arr); + free_value(sep); + push_value(vm, out); + break; + } + + case OP_SUBSTR: { + Value lenv = pop_value(vm); + Value startv = pop_value(vm); + Value str = pop_value(vm); + if (str.type != VAL_STRING || startv.type != VAL_INT || lenv.type != VAL_INT) { + fprintf(stderr, "Runtime type error: SUBSTR expects (string, int, int)\n"); + exit(1); + } + char *s = string_substr(str.s ? str.s : "", (int)startv.i, (int)lenv.i); + Value out = make_string(s ? s : ""); + if (s) free(s); + free_value(str); + free_value(startv); + free_value(lenv); + push_value(vm, out); + break; + } + + case OP_FIND: { + Value needle = pop_value(vm); + Value hay = pop_value(vm); + if (hay.type != VAL_STRING || needle.type != VAL_STRING) { + fprintf(stderr, "Runtime type error: FIND expects (string, string)\n"); + exit(1); + } + int idx = string_find(hay.s ? hay.s : "", needle.s ? needle.s : ""); + free_value(hay); + free_value(needle); + push_value(vm, make_int(idx)); + break; + } + + case OP_CONTAINS: { + Value needle = pop_value(vm); + Value arr = pop_value(vm); + if (arr.type != VAL_ARRAY) { + fprintf(stderr, "Runtime type error: CONTAINS expects (array, value)\n"); + exit(1); + } + int ok = array_contains(&arr, &needle); + free_value(arr); + free_value(needle); + push_value(vm, make_int(ok ? 1 : 0)); + break; + } + + case OP_INDEX_OF: { + Value needle = pop_value(vm); + Value arr = pop_value(vm); + if (arr.type != VAL_ARRAY) { + fprintf(stderr, "Runtime type error: INDEX_OF expects (array, value)\n"); + exit(1); + } + int idx = array_index_of(&arr, &needle); + free_value(arr); + free_value(needle); + push_value(vm, make_int(idx)); + break; + } + + case OP_CLEAR: { + Value arr = pop_value(vm); + if (arr.type != VAL_ARRAY) { + fprintf(stderr, "Runtime type error: CLEAR expects array\n"); + exit(1); + } + array_clear(&arr); + free_value(arr); + push_value(vm, make_int(0)); + break; + } + + case OP_ENUMERATE: { + Value arr = pop_value(vm); + if (arr.type != VAL_ARRAY) { + fprintf(stderr, "Runtime type error: ENUMERATE expects array\n"); + exit(1); + } + int n = array_length(&arr); + if (n < 0) n = 0; + Value *pairs = (Value*)malloc(sizeof(Value) * n); + for (int i = 0; i < n; ++i) { + Value elem; + array_get_copy(&arr, i, &elem); + Value kv_vals[2]; + kv_vals[0] = make_int(i); + kv_vals[1] = elem; + Value kv = make_array_from_values(kv_vals, 2); + /* kv_vals[1] (elem) already moved, free temp copies: */ + free_value(kv_vals[0]); + free_value(kv_vals[1]); + pairs[i] = kv; + } + Value out = make_array_from_values(pairs, n); + for (int i = 0; i < n; ++i) free_value(pairs[i]); + free(pairs); + free_value(arr); + push_value(vm, out); + break; + } + + case OP_ZIP: { + Value b = pop_value(vm); + Value a = pop_value(vm); + if (a.type != VAL_ARRAY || b.type != VAL_ARRAY) { + fprintf(stderr, "Runtime type error: ZIP expects (array, array)\n"); + exit(1); + } + int na = array_length(&a); + int nb = array_length(&b); + int n = na < nb ? na : nb; + if (n < 0) n = 0; + Value *pairs = (Value*)malloc(sizeof(Value) * n); + for (int i = 0; i < n; ++i) { + Value av, bv; + array_get_copy(&a, i, &av); + array_get_copy(&b, i, &bv); + Value kv_vals[2]; + kv_vals[0] = av; + kv_vals[1] = bv; + Value kv = make_array_from_values(kv_vals, 2); + free_value(kv_vals[0]); + free_value(kv_vals[1]); + pairs[i] = kv; + } + Value out = make_array_from_values(pairs, n); + for (int i = 0; i < n; ++i) free_value(pairs[i]); + free(pairs); + free_value(a); + free_value(b); + push_value(vm, out); + break; + } + + case OP_MIN: { + Value b = pop_value(vm); + Value a = pop_value(vm); + if (a.type != VAL_INT || b.type != VAL_INT) { fprintf(stderr, "MIN expects ints\n"); exit(1); } + push_value(vm, make_int(a.i < b.i ? a.i : b.i)); + free_value(a); free_value(b); + break; + } + + case OP_MAX: { + Value b = pop_value(vm); + Value a = pop_value(vm); + if (a.type != VAL_INT || b.type != VAL_INT) { fprintf(stderr, "MAX expects ints\n"); exit(1); } + push_value(vm, make_int(a.i > b.i ? a.i : b.i)); + free_value(a); free_value(b); + break; + } + + case OP_CLAMP: { + Value hi = pop_value(vm); + Value lo = pop_value(vm); + Value x = pop_value(vm); + if (x.type != VAL_INT || lo.type != VAL_INT || hi.type != VAL_INT) { fprintf(stderr, "CLAMP expects ints\n"); exit(1); } + int64_t v = x.i; + if (v < lo.i) v = lo.i; + if (v > hi.i) v = hi.i; + push_value(vm, make_int(v)); + free_value(x); free_value(lo); free_value(hi); + break; + } + + case OP_ABS: { + Value x = pop_value(vm); + if (x.type != VAL_INT) { fprintf(stderr, "ABS expects int\n"); exit(1); } + int64_t v = x.i; + if (v < 0) v = -v; + push_value(vm, make_int(v)); + free_value(x); + break; + } + + case OP_POW: { + Value b = pop_value(vm); + Value a = pop_value(vm); + if (a.type != VAL_INT || b.type != VAL_INT) { fprintf(stderr, "POW expects ints\n"); exit(1); } + /* simple integer pow */ + int64_t base = a.i; + int64_t exp = b.i; + int64_t res = 1; + if (exp < 0) { res = 0; } else { + while (exp > 0) { + if (exp & 1) res *= base; + base *= base; + exp >>= 1; + } + } + push_value(vm, make_int(res)); + free_value(a); free_value(b); + break; + } + + case OP_RANDOM_SEED: { + Value seed = pop_value(vm); + if (seed.type != VAL_INT) { fprintf(stderr, "RANDOM_SEED expects int\n"); exit(1); } + srand((unsigned int)seed.i); + free_value(seed); + push_value(vm, make_int(0)); + break; + } + + case OP_RANDOM_INT: { + Value hi = pop_value(vm); + Value lo = pop_value(vm); + if (lo.type != VAL_INT || hi.type != VAL_INT) { fprintf(stderr, "RANDOM_INT expects (int, int)\n"); exit(1); } + int64_t a = lo.i, b = hi.i; + if (b <= a) { push_value(vm, make_int((int64_t)a)); free_value(lo); free_value(hi); break; } + int64_t span = b - a; + int64_t r = (int64_t)(rand() % (span)); + push_value(vm, make_int(a + r)); + free_value(lo); free_value(hi); + 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 0773c47..958731b 100644 --- a/src/vm.h +++ b/src/vm.h @@ -17,7 +17,11 @@ static const char *opcode_names[] = { "MOD","AND","OR","NOT","DUP","SWAP", "MAKE_ARRAY","INDEX_GET","INDEX_SET", "LEN","ARR_PUSH","ARR_POP","ARR_SET","ARR_INSERT","ARR_REMOVE","SLICE", - "TO_NUMBER","TO_STRING" + "TO_NUMBER","TO_STRING", + "SPLIT","JOIN","SUBSTR","FIND", + "CONTAINS","INDEX_OF","CLEAR", + "ENUMERATE","ZIP", + "MIN","MAX","CLAMP","ABS","POW","RANDOM_SEED","RANDOM_INT" }; typedef struct { @@ -58,7 +62,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_TO_STRING; // all current opcodes + return op >= OP_NOP && op <= OP_RANDOM_INT; // all current opcodes } #endif