More built-ins.
This commit is contained in:
parent
559400a866
commit
a4f4fcba99
8 changed files with 652 additions and 3 deletions
49
examples/builtins_extended.fun
Executable file
49
examples/builtins_extended.fun
Executable 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
|
||||
*/
|
||||
|
|
@ -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 "???";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
147
src/parser.c
147
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) {
|
||||
|
|
|
|||
150
src/value.c
150
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
12
src/value.h
12
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
|
||||
|
||||
|
|
|
|||
247
src/vm.c
247
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) {
|
||||
|
|
|
|||
8
src/vm.h
8
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue