1
0
Fork 0
forked from fun/fun

More built-ins.

This commit is contained in:
Johannes Findeisen 2025-09-15 03:59:03 +02:00
commit a4f4fcba99
8 changed files with 652 additions and 3 deletions

49
examples/builtins_extended.fun Executable file
View file

@ -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
*/

View file

@ -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 "???";
}
}

View file

@ -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 {

View file

@ -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) {

View file

@ -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;
}

View file

@ -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

247
src/vm.c
View file

@ -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) {

View file

@ -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