1
0
Fork 0
forked from fun/fun

Tons of refactoring and some fixes and enhancements... ;)

This commit is contained in:
Johannes Findeisen 2025-09-15 06:31:32 +02:00
commit 13919ad87d
82 changed files with 1775 additions and 1382 deletions

View file

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.16)
project(fun VERSION 0.2.0 LANGUAGES C)
project(fun VERSION 0.2.1 LANGUAGES C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)

View file

@ -48,3 +48,14 @@ repl: fun
# Convenience: run a script (usage: make run SCRIPT=examples/strings_test.fun)
run: fun
./$(BUILD_DIR)/fun $(SCRIPT)
# Additional convenience targets
.PHONY: verify-ops examples run-examples
# Verify that each OP_* in bytecode.h has a corresponding vm_case_*.inc include in vm.c
verify-ops:
@./scripts/check_op_includes.py --verbose
# Build and run all examples (searches for the fun binary automatically)
examples run-examples: fun
@./scripts/run_examples.sh

14
README_DEV.md Normal file
View file

@ -0,0 +1,14 @@
# Developer Notes
This project splits VM opcode handlers into one-include-per-op file for readability.
The main interpreter loop (`src/vm.c`, `vm_run`) includes `vm_case_*.inc` files,
each containing a single `case OP_*: { ... }` handler.
## Adding a new opcode
1. Add the enum in `src/bytecode.h` (e.g., `OP_FOO`) and add its human-readable name
to `opcode_names[]` in `src/vm.h`. Update `opcode_is_valid` upper bound if needed.
2. Implement the VM handler in `src/vm_case_foo.inc` using the established style:
- No function wrappers, just `case OP_FOO: { ... break; }`.
3. Include the new file in the switch inside `src/vm.c` (`vm_run`), near similar ops.
4. Run the checker script:

0
examples/arrays_iter.fun Normal file → Executable file
View file

0
examples/builtins_conversions.fun Normal file → Executable file
View file

0
examples/nested_loops.fun Normal file → Executable file
View file

98
scripts/check_op_includes.py Executable file
View file

@ -0,0 +1,98 @@
#!/usr/bin/env python3
import re
import sys
import argparse
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
BYTECODE = ROOT / "src" / "bytecode.h"
VM_C = ROOT / "src" / "vm.c"
OP_RE = re.compile(r'\bOP_([A-Z0-9_]+)\b')
INCLUDE_RE = re.compile(r'#include\s+"vm_case_([a-z0-9_]+)\.inc"')
def read_text(path: Path) -> str:
try:
return path.read_text(encoding="utf-8")
except Exception as e:
print(f"error: cannot read {path}: {e}", file=sys.stderr)
sys.exit(2)
def parse_opcodes_from_bytecode(text: str) -> set[str]:
# Gather OP_* from the enum definition, excluding duplicates in comments
ops = set()
# crude: find typedef enum block and extract OP_* tokens
enum_start = text.find("typedef enum")
if enum_start == -1:
enum_start = 0
enum_block = text[enum_start:]
for m in OP_RE.finditer(enum_block):
token = m.group(1)
ops.add(token)
return ops
def parse_includes_from_vm(text: str) -> set[str]:
incs = set(m.group(1) for m in INCLUDE_RE.finditer(text))
# Convert include base names back to uppercase opcode tokens heuristically.
mapping = {
"nop":"NOP","halt":"HALT",
"load_const":"LOAD_CONST","load_local":"LOAD_LOCAL","store_local":"STORE_LOCAL",
"load_global":"LOAD_GLOBAL","store_global":"STORE_GLOBAL",
"pop":"POP","dup":"DUP","swap":"SWAP",
"call":"CALL","return":"RETURN","print":"PRINT","jump":"JUMP","jump_if_false":"JUMP_IF_FALSE",
"add":"ADD","sub":"SUB","mul":"MUL","div":"DIV",
"mod":"MOD","lt":"LT","lte":"LTE","gt":"GT","gte":"GTE",
"eq":"EQ","neq":"NEQ","and":"AND","or":"OR","not":"NOT",
"make_array":"MAKE_ARRAY","len":"LEN",
"index_get":"INDEX_GET","index_set":"INDEX_SET",
"arr_push":"ARR_PUSH","arr_pop":"ARR_POP","arr_set":"ARR_SET","arr_insert":"ARR_INSERT","arr_remove":"ARR_REMOVE",
"slice":"SLICE",
"to_number":"TO_NUMBER","to_string":"TO_STRING",
"split":"SPLIT","join":"JOIN","substr":"SUBSTR","find":"FIND",
"enumerate":"ENUMERATE","zip":"ZIP",
"min":"MIN","max":"MAX","clamp":"CLAMP","abs":"ABS","pow":"POW",
"random_seed":"RANDOM_SEED","random_int":"RANDOM_INT",
"make_map":"MAKE_MAP","keys":"KEYS","values":"VALUES","has_key":"HAS_KEY",
"read_file":"READ_FILE","write_file":"WRITE_FILE",
}
tokens = set()
for inc in incs:
tokens.add(mapping.get(inc, inc.upper()))
return tokens
def main() -> int:
ap = argparse.ArgumentParser(description="Check that vm_case_*.inc includes cover OP_* opcodes.")
ap.add_argument("-v", "--verbose", action="store_true", help="print summary of ops and includes")
args = ap.parse_args()
bc_text = read_text(BYTECODE)
vm_text = read_text(VM_C)
ops = parse_opcodes_from_bytecode(bc_text)
inc_tokens = parse_includes_from_vm(vm_text)
missing_handlers = sorted([op for op in ops if op not in inc_tokens])
extra_includes = sorted([t for t in inc_tokens if t not in ops])
ok = True
if missing_handlers:
ok = False
print("Missing vm_case includes for opcodes:", ", ".join(missing_handlers))
if extra_includes:
ok = False
print("Includes without matching OP_* in bytecode.h:", ", ".join(extra_includes))
if ok:
if args.verbose:
print(f"OK: vm_case includes cover OP_* set from bytecode.h")
print(f" OP_* count : {len(ops)}")
print(f" includes count : {len(inc_tokens)}")
print(f" OP_* tokens : {', '.join(sorted(ops))}")
print(f" include tokens : {', '.join(sorted(inc_tokens))}")
else:
print("OK: vm_case includes cover OP_* set from bytecode.h")
return 0
return 1
if __name__ == "__main__":
sys.exit(main())

69
scripts/run_examples.sh Executable file
View file

@ -0,0 +1,69 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")"/.. && pwd)"
EX_DIR="$ROOT/examples"
# Allow override via first arg or FUN_BIN env
BIN="${1:-${FUN_BIN:-}}"
pick_bin() {
local cands=(
"$ROOT/fun"
"$ROOT/build/fun"
"$ROOT/build/bin/fun"
"$ROOT/cmake-build-debug/fun"
"$ROOT/cmake-build-release/fun"
)
for p in "${cands[@]}"; do
[[ -x "$p" ]] && { echo "$p"; return 0; }
done
# Last resort: search shallowly for an executable named 'fun' under common build dirs
local found
found="$(
find "$ROOT" -maxdepth 3 -type f -name fun -perm -u+x 2>/dev/null \
| grep -E '/(build|cmake-build[^/]*)/fun$' \
| head -n 1 || true
)"
[[ -n "$found" ]] && { echo "$found"; return 0; }
return 1
}
if [[ -z "${BIN}" ]]; then
if ! BIN="$(pick_bin)"; then
echo "error: fun binary not found. Try building it (e.g., via CMake) or pass it explicitly:" >&2
echo " scripts/run_examples.sh /path/to/fun" >&2
echo "or set FUN_BIN=/path/to/fun" >&2
exit 2
fi
fi
if [[ ! -x "$BIN" ]]; then
echo "error: fun binary not found or not executable at $BIN" >&2
exit 2
fi
shopt -s nullglob
files=("$EX_DIR"/*.fun)
shopt -u nullglob
if (( ${#files[@]} == 0 )); then
echo "No .fun example files found in $EX_DIR"
exit 0
fi
rc=0
for f in "${files[@]}"; do
echo "=== Running: ${f#$ROOT/} ==="
if ! "$BIN" "$f"; then
echo "FAILED: ${f#$ROOT/}"
rc=1
fi
done
if (( rc == 0 )); then
echo "All examples ran successfully."
else
echo "Some examples failed."
fi
exit $rc

49
src/array_utils.c Normal file
View file

@ -0,0 +1,49 @@
#include "value.h"
#include <string.h>
/* array utilities */
int array_contains(const Value *v, const Value *needle) {
if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
int n = array_length(v);
for (int i = 0; i < n; ++i) {
Value item;
if (array_get_copy(v, i, &item)) {
int eq = value_equals(&item, needle);
free_value(item);
if (eq) return 1;
}
}
return 0;
}
int array_index_of(const Value *v, const Value *needle) {
if (!v || v->type != VAL_ARRAY || !v->arr) return -1;
int n = array_length(v);
for (int i = 0; i < n; ++i) {
Value item;
if (array_get_copy(v, i, &item)) {
int eq = value_equals(&item, needle);
free_value(item);
if (eq) return i;
}
}
return -1;
}
void array_clear(Value *v) {
if (!v || v->type != VAL_ARRAY || !v->arr) return;
int n = array_length(v);
for (int i = 0; i < n; ++i) {
Value item;
if (array_get_copy(v, i, &item)) {
free_value(item);
}
}
/* internal clear uses public API to reset to zero length */
/* Since we don't expose capacity, emulate by popping elements */
Value out;
while (array_pop(v, &out)) {
free_value(out);
}
}

31
src/builtins_io.c Normal file
View file

@ -0,0 +1,31 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* File I/O helpers: return malloc'd buffers or status ints */
char *bio_read_file_strdup(const char *path) {
if (!path) return strdup("");
FILE *f = fopen(path, "rb");
if (!f) return strdup("");
if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return strdup(""); }
long sz = ftell(f);
if (sz < 0) { fclose(f); return strdup(""); }
rewind(f);
char *buf = (char*)malloc((size_t)sz + 1);
if (!buf) { fclose(f); return strdup(""); }
size_t n = fread(buf, 1, (size_t)sz, f);
fclose(f);
buf[n] = '\0';
return buf;
}
int bio_write_file(const char *path, const char *data, size_t len) {
if (!path) return 0;
FILE *f = fopen(path, "wb");
if (!f) return 0;
if (!data) data = "";
int ok = (fwrite(data, 1, len, f) == len);
fclose(f);
return ok;
}

51
src/builtins_iter.c Normal file
View file

@ -0,0 +1,51 @@
#include "value.h"
#include <stdlib.h>
/* enumerate(arr) -> [[0, v0], [1, v1], ...] */
Value bi_enumerate(const Value *arr) {
int n = array_length(arr);
if (n <= 0) return make_array_from_values(NULL, 0);
Value *pairs = (Value*)malloc(sizeof(Value) * n);
if (!pairs) return make_array_from_values(NULL, 0);
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);
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);
return out;
}
/* zip(a, b) -> [[a0,b0], [a1,b1], ...] up to min(len(a),len(b)) */
Value bi_zip(const Value *a, const Value *b) {
int na = array_length(a);
int nb = array_length(b);
int n = na < nb ? na : nb;
if (n <= 0) return make_array_from_values(NULL, 0);
Value *pairs = (Value*)malloc(sizeof(Value) * n);
if (!pairs) return make_array_from_values(NULL, 0);
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);
return out;
}

37
src/builtins_math.c Normal file
View file

@ -0,0 +1,37 @@
#include <stdint.h>
#include <stdlib.h>
/* Math built-ins (pure helpers) */
int64_t bm_min(int64_t a, int64_t b) { return a < b ? a : b; }
int64_t bm_max(int64_t a, int64_t b) { return a > b ? a : b; }
int64_t bm_clamp(int64_t x, int64_t lo, int64_t hi) {
if (x < lo) return lo;
if (x > hi) return hi;
return x;
}
int64_t bm_abs(int64_t x) { return x < 0 ? -x : x; }
int64_t bm_pow(int64_t base, int64_t exp) {
if (exp < 0) return 0; /* integer pow: negative exponent -> 0 */
int64_t res = 1;
int64_t b = base;
int64_t e = exp;
while (e > 0) {
if (e & 1) res *= b;
b *= b;
e >>= 1;
}
return res;
}
/* RNG: thin wrappers over libc's srand/rand for now */
void bm_random_seed(unsigned int seed) { srand(seed); }
int64_t bm_random_int(int64_t lo, int64_t hi) {
if (hi <= lo) return lo;
int64_t span = hi - lo;
return lo + (rand() % span);
}

32
src/builtins_string.c Normal file
View file

@ -0,0 +1,32 @@
#include "value.h"
#include <stdlib.h>
/* String built-ins wrappers used by VM opcodes */
Value bi_split(const Value *str, const Value *sep) {
const char *s = (str && str->type == VAL_STRING && str->s) ? str->s : "";
const char *p = (sep && sep->type == VAL_STRING && sep->s) ? sep->s : "";
return string_split_to_array(s, p);
}
Value bi_join(const Value *arr, const Value *sep) {
const char *p = (sep && sep->type == VAL_STRING && sep->s) ? sep->s : "";
char *s = array_join_with_sep(arr, p);
Value out = make_string(s ? s : "");
if (s) free(s);
return out;
}
Value bi_substr(const Value *str, int start, int len) {
const char *s = (str && str->type == VAL_STRING && str->s) ? str->s : "";
char *sub = string_substr(s, start, len);
Value out = make_string(sub ? sub : "");
if (sub) free(sub);
return out;
}
int bi_find(const Value *hay, const Value *needle) {
const char *h = (hay && hay->type == VAL_STRING && hay->s) ? hay->s : "";
const char *n = (needle && needle->type == VAL_STRING && needle->s) ? needle->s : "";
return string_find(h, n);
}

107
src/map.c Normal file
View file

@ -0,0 +1,107 @@
#include "value.h"
#include <stdlib.h>
#include <string.h>
/* Internal Map definition; Value holds struct Map* */
typedef struct Map {
int refcount;
int count;
int cap;
char **keys; /* each key owned here */
Value *vals; /* each value owned here */
} Map;
Value make_map_empty(void) {
Map *m = (Map*)malloc(sizeof(Map));
if (!m) return make_nil();
m->refcount = 1;
m->count = 0;
m->cap = 0;
m->keys = NULL;
m->vals = NULL;
Value v;
v.type = VAL_MAP;
v.map = (struct Map*)m;
return v;
}
static int map_ensure_cap(Map *m, int need) {
if (m->cap >= need) return 1;
int ncap = m->cap == 0 ? 4 : m->cap * 2;
while (ncap < need) ncap *= 2;
char **nkeys = (char**)realloc(m->keys, sizeof(char*) * ncap);
Value *nvals = (Value*)realloc(m->vals, sizeof(Value) * ncap);
if (!nkeys || !nvals) return 0;
m->keys = nkeys;
m->vals = nvals;
m->cap = ncap;
return 1;
}
int map_set(Value *vm, const char *key, Value v) {
if (!vm || vm->type != VAL_MAP || !vm->map || !key) { free_value(v); return 0; }
Map *m = (Map*)vm->map;
for (int i = 0; i < m->count; ++i) {
if (strcmp(m->keys[i], key) == 0) {
free_value(m->vals[i]);
m->vals[i] = v;
return 1;
}
}
if (!map_ensure_cap(m, m->count + 1)) { free_value(v); return 0; }
m->keys[m->count] = strdup(key);
m->vals[m->count] = v;
m->count++;
return 1;
}
int map_get_copy(const Value *vm, const char *key, Value *out) {
if (!vm || vm->type != VAL_MAP || !vm->map || !key) return 0;
Map *m = (Map*)vm->map;
for (int i = 0; i < m->count; ++i) {
if (strcmp(m->keys[i], key) == 0) {
if (out) *out = copy_value(&m->vals[i]);
return 1;
}
}
return 0;
}
int map_has(const Value *vm, const char *key) {
if (!vm || vm->type != VAL_MAP || !vm->map || !key) return 0;
Map *m = (Map*)vm->map;
for (int i = 0; i < m->count; ++i) {
if (strcmp(m->keys[i], key) == 0) return 1;
}
return 0;
}
Value map_keys_array(const Value *vm) {
if (!vm || vm->type != VAL_MAP || !vm->map) return make_array_from_values(NULL, 0);
Map *m = (Map*)vm->map;
if (m->count <= 0) return make_array_from_values(NULL, 0);
Value *tmp = (Value*)malloc(sizeof(Value) * m->count);
if (!tmp) return make_array_from_values(NULL, 0);
for (int i = 0; i < m->count; ++i) {
tmp[i] = make_string(m->keys[i]);
}
Value arr = make_array_from_values(tmp, m->count);
for (int i = 0; i < m->count; ++i) free_value(tmp[i]);
free(tmp);
return arr;
}
Value map_values_array(const Value *vm) {
if (!vm || vm->type != VAL_MAP || !vm->map) return make_array_from_values(NULL, 0);
Map *m = (Map*)vm->map;
if (m->count <= 0) return make_array_from_values(NULL, 0);
Value *tmp = (Value*)malloc(sizeof(Value) * m->count);
if (!tmp) return make_array_from_values(NULL, 0);
for (int i = 0; i < m->count; ++i) {
tmp[i] = copy_value(&m->vals[i]);
}
Value arr = make_array_from_values(tmp, m->count);
for (int i = 0; i < m->count; ++i) free_value(tmp[i]);
free(tmp);
return arr;
}

View file

@ -39,170 +39,7 @@ static void calc_line_col(const char *src, size_t len, size_t pos, int *out_line
/* --------------------------- */
static char *read_file_all(const char *path, size_t *out_len) {
FILE *f = fopen(path, "rb");
if (!f) return NULL;
if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return NULL; }
long sz = ftell(f);
if (sz < 0) { fclose(f); return NULL; }
rewind(f);
char *buf = (char*)malloc((size_t)sz + 1);
if (!buf) { fclose(f); return NULL; }
size_t n = fread(buf, 1, (size_t)sz, f);
fclose(f);
buf[n] = '\0';
if (out_len) *out_len = n;
return buf;
}
static void skip_ws(const char *src, size_t len, size_t *pos) {
while (*pos < len) {
char c = src[*pos];
if (c == ' ' || c == '\t' || c == '\r' || c == '\n') { (*pos)++; continue; }
break;
}
}
static void skip_line(const char *src, size_t len, size_t *pos) {
while (*pos < len && src[*pos] != '\n') (*pos)++;
if (*pos < len && src[*pos] == '\n') (*pos)++;
}
static void skip_comments(const char *src, size_t len, size_t *pos) {
for (;;) {
skip_ws(src, len, pos);
if (*pos + 1 < len && src[*pos] == '/' && src[*pos + 1] == '/') {
*pos += 2;
skip_line(src, len, pos);
continue;
}
if (*pos + 1 < len && src[*pos] == '/' && src[*pos + 1] == '*') {
*pos += 2;
while (*pos + 1 < len && !(src[*pos] == '*' && src[*pos + 1] == '/')) (*pos)++;
if (*pos + 1 < len) *pos += 2;
continue;
}
break;
}
}
static int starts_with(const char *src, size_t len, size_t pos, const char *kw) {
size_t klen = strlen(kw);
if (pos + klen > len) return 0;
return strncmp(src + pos, kw, klen) == 0;
}
static void skip_shebang_if_present(const char *src, size_t len, size_t *pos) {
if (*pos == 0 && starts_with(src, len, *pos, "#!")) {
skip_line(src, len, pos);
}
}
static void skip_identifier(const char *src, size_t len, size_t *pos) {
size_t p = *pos;
if (p < len && (isalpha((unsigned char)src[p]) || src[p] == '_')) {
p++;
while (p < len && (isalnum((unsigned char)src[p]) || src[p] == '_')) p++;
}
*pos = p;
}
static int consume_char(const char *src, size_t len, size_t *pos, char expected) {
skip_ws(src, len, pos);
if (*pos < len && src[*pos] == expected) { (*pos)++; return 1; }
return 0;
}
static char *parse_string_literal_any_quote(const char *src, size_t len, size_t *pos) {
skip_ws(src, len, pos);
if (*pos >= len) return NULL;
char quote = src[*pos];
if (quote != '"' && quote != '\'') return NULL;
(*pos)++; // skip opening quote
size_t cap = 64, out_len = 0;
char *out = (char*)malloc(cap);
if (!out) return NULL;
while (*pos < len) {
char c = src[*pos];
if (c == quote) { (*pos)++; break; }
if (c == '\\') {
(*pos)++;
if (*pos >= len) break;
char e = src[*pos];
switch (e) {
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case '\\': c = '\\'; break;
case '"': c = '"'; break;
case '\'': c = '\''; break;
default: c = e; break;
}
}
if (out_len + 1 >= cap) {
cap *= 2;
char *tmp = (char*)realloc(out, cap);
if (!tmp) { free(out); return NULL; }
out = tmp;
}
out[out_len++] = c;
(*pos)++;
}
if (out_len + 1 >= cap) {
char *tmp = (char*)realloc(out, cap + 1);
if (!tmp) { free(out); return NULL; }
out = tmp;
}
out[out_len] = '\0';
return out;
}
/* === Helpers for identifiers, numbers, booleans, and globals === */
static void skip_spaces(const char *src, size_t len, size_t *pos) {
while (*pos < len) {
char c = src[*pos];
if (c == ' ' || c == '\t' || c == '\r') { (*pos)++; continue; }
break;
}
}
static int read_identifier_into(const char *src, size_t len, size_t *pos, char **out_name) {
size_t p = *pos;
if (p < len && (isalpha((unsigned char)src[p]) || src[p] == '_')) {
size_t start = p;
p++;
while (p < len && (isalnum((unsigned char)src[p]) || src[p] == '_')) p++;
size_t n = p - start;
char *name = (char*)malloc(n + 1);
if (!name) return 0;
memcpy(name, src + start, n);
name[n] = '\0';
*pos = p;
*out_name = name;
return 1;
}
return 0;
}
static int64_t parse_int_literal_value(const char *src, size_t len, size_t *pos, int *ok) {
size_t p = *pos;
skip_spaces(src, len, &p);
int sign = 1;
if (p < len && (src[p] == '+' || src[p] == '-')) {
if (src[p] == '-') sign = -1;
p++;
}
if (p >= len || !isdigit((unsigned char)src[p])) { *ok = 0; return 0; }
int64_t val = 0;
while (p < len && isdigit((unsigned char)src[p])) {
val = val * 10 + (src[p] - '0');
p++;
}
*pos = p;
*ok = 1;
return sign * val;
}
#include "parser_utils.c"
/* very small global symbol table for LOAD_GLOBAL/STORE_GLOBAL */
static struct {

172
src/parser_utils.c Normal file
View file

@ -0,0 +1,172 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "parser.h"
#include "value.h"
#include "vm.h"
static char *read_file_all(const char *path, size_t *out_len) {
FILE *f = fopen(path, "rb");
if (!f) return NULL;
if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return NULL; }
long sz = ftell(f);
if (sz < 0) { fclose(f); return NULL; }
rewind(f);
char *buf = (char*)malloc((size_t)sz + 1);
if (!buf) { fclose(f); return NULL; }
size_t n = fread(buf, 1, (size_t)sz, f);
fclose(f);
buf[n] = '\0';
if (out_len) *out_len = n;
return buf;
}
static void skip_ws(const char *src, size_t len, size_t *pos) {
while (*pos < len) {
char c = src[*pos];
if (c == ' ' || c == '\t' || c == '\r' || c == '\n') { (*pos)++; continue; }
break;
}
}
static void skip_line(const char *src, size_t len, size_t *pos) {
while (*pos < len && src[*pos] != '\n') (*pos)++;
if (*pos < len && src[*pos] == '\n') (*pos)++;
}
static void skip_comments(const char *src, size_t len, size_t *pos) {
for (;;) {
skip_ws(src, len, pos);
if (*pos + 1 < len && src[*pos] == '/' && src[*pos + 1] == '/') {
*pos += 2;
skip_line(src, len, pos);
continue;
}
if (*pos + 1 < len && src[*pos] == '/' && src[*pos + 1] == '*') {
*pos += 2;
while (*pos + 1 < len && !(src[*pos] == '*' && src[*pos + 1] == '/')) (*pos)++;
if (*pos + 1 < len) *pos += 2;
continue;
}
break;
}
}
static int starts_with(const char *src, size_t len, size_t pos, const char *kw) {
size_t klen = strlen(kw);
if (pos + klen > len) return 0;
return strncmp(src + pos, kw, klen) == 0;
}
static void skip_shebang_if_present(const char *src, size_t len, size_t *pos) {
if (*pos == 0 && starts_with(src, len, *pos, "#!")) {
skip_line(src, len, pos);
}
}
static void skip_identifier(const char *src, size_t len, size_t *pos) {
size_t p = *pos;
if (p < len && (isalpha((unsigned char)src[p]) || src[p] == '_')) {
p++;
while (p < len && (isalnum((unsigned char)src[p]) || src[p] == '_')) p++;
}
*pos = p;
}
static int consume_char(const char *src, size_t len, size_t *pos, char expected) {
skip_ws(src, len, pos);
if (*pos < len && src[*pos] == expected) { (*pos)++; return 1; }
return 0;
}
static char *parse_string_literal_any_quote(const char *src, size_t len, size_t *pos) {
skip_ws(src, len, pos);
if (*pos >= len) return NULL;
char quote = src[*pos];
if (quote != '"' && quote != '\'') return NULL;
(*pos)++; // skip opening quote
size_t cap = 64, out_len = 0;
char *out = (char*)malloc(cap);
if (!out) return NULL;
while (*pos < len) {
char c = src[*pos];
if (c == quote) { (*pos)++; break; }
if (c == '\\') {
(*pos)++;
if (*pos >= len) break;
char e = src[*pos];
switch (e) {
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case '\\': c = '\\'; break;
case '"': c = '"'; break;
case '\'': c = '\''; break;
default: c = e; break;
}
}
if (out_len + 1 >= cap) {
cap *= 2;
char *tmp = (char*)realloc(out, cap);
if (!tmp) { free(out); return NULL; }
out = tmp;
}
out[out_len++] = c;
(*pos)++;
}
if (out_len + 1 >= cap) {
char *tmp = (char*)realloc(out, cap + 1);
if (!tmp) { free(out); return NULL; }
out = tmp;
}
out[out_len] = '\0';
return out;
}
/* === Helpers for identifiers, numbers, booleans, and globals === */
static void skip_spaces(const char *src, size_t len, size_t *pos) {
while (*pos < len) {
char c = src[*pos];
if (c == ' ' || c == '\t' || c == '\r') { (*pos)++; continue; }
break;
}
}
static int read_identifier_into(const char *src, size_t len, size_t *pos, char **out_name) {
size_t p = *pos;
if (p < len && (isalpha((unsigned char)src[p]) || src[p] == '_')) {
size_t start = p;
p++;
while (p < len && (isalnum((unsigned char)src[p]) || src[p] == '_')) p++;
size_t n = p - start;
char *name = (char*)malloc(n + 1);
if (!name) return 0;
memcpy(name, src + start, n);
name[n] = '\0';
*pos = p;
*out_name = name;
return 1;
}
return 0;
}
static int64_t parse_int_literal_value(const char *src, size_t len, size_t *pos, int *ok) {
size_t p = *pos;
skip_spaces(src, len, &p);
int sign = 1;
if (p < len && (src[p] == '+' || src[p] == '-')) {
if (src[p] == '-') sign = -1;
p++;
}
if (p >= len || !isdigit((unsigned char)src[p])) { *ok = 0; return 0; }
int64_t val = 0;
while (p < len && isdigit((unsigned char)src[p])) {
val = val * 10 + (src[p] - '0');
p++;
}
*pos = p;
*ok = 1;
return sign * val;
}

125
src/str_utils.c Normal file
View file

@ -0,0 +1,125 @@
#include "value.h"
#include <stdlib.h>
#include <string.h>
/* string helpers returning newly allocated C strings or arrays */
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 = "";
/* Array is defined in value.c; we only need safe public access. */
const int n = array_length(v);
if (n <= 0) return strdup("");
char **parts = (char**)malloc(sizeof(char*) * n);
if (!parts) return strdup("");
size_t total = 0;
for (int i = 0; i < n; ++i) {
Value item;
if (!array_get_copy(v, i, &item)) {
parts[i] = strdup("");
} else {
parts[i] = value_to_string_alloc(&item);
free_value(item);
}
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

@ -3,6 +3,10 @@
#include <string.h>
#include <stdio.h>
/* Compile helper implementations into this TU to avoid build system changes */
#include "str_utils.c"
#include "array_utils.c"
typedef struct Array {
int refcount;
int count;
@ -45,99 +49,6 @@ Value make_nil(void) {
return v;
}
Value make_map_empty(void) {
Map *m = (Map*)malloc(sizeof(Map));
if (!m) return make_nil();
m->refcount = 1;
m->count = 0;
m->cap = 0;
m->keys = NULL;
m->vals = NULL;
Value v;
v.type = VAL_MAP;
v.map = (struct Map*)m;
return v;
}
static int map_ensure_cap(Map *m, int need) {
if (m->cap >= need) return 1;
int ncap = m->cap == 0 ? 4 : m->cap * 2;
while (ncap < need) ncap *= 2;
char **nkeys = (char**)realloc(m->keys, sizeof(char*) * ncap);
Value *nvals = (Value*)realloc(m->vals, sizeof(Value) * ncap);
if (!nkeys || !nvals) return 0;
m->keys = nkeys;
m->vals = nvals;
m->cap = ncap;
return 1;
}
int map_set(Value *vm, const char *key, Value v) {
if (!vm || vm->type != VAL_MAP || !vm->map || !key) { free_value(v); return 0; }
Map *m = (Map*)vm->map;
/* replace if exists */
for (int i = 0; i < m->count; ++i) {
if (strcmp(m->keys[i], key) == 0) {
free_value(m->vals[i]);
m->vals[i] = v;
return 1;
}
}
if (!map_ensure_cap(m, m->count + 1)) { free_value(v); return 0; }
m->keys[m->count] = strdup(key);
m->vals[m->count] = v;
m->count++;
return 1;
}
int map_get_copy(const Value *vm, const char *key, Value *out) {
if (!vm || vm->type != VAL_MAP || !vm->map || !key) return 0;
Map *m = (Map*)vm->map;
for (int i = 0; i < m->count; ++i) {
if (strcmp(m->keys[i], key) == 0) {
if (out) *out = copy_value(&m->vals[i]);
return 1;
}
}
return 0;
}
int map_has(const Value *vm, const char *key) {
if (!vm || vm->type != VAL_MAP || !vm->map || !key) return 0;
Map *m = (Map*)vm->map;
for (int i = 0; i < m->count; ++i) {
if (strcmp(m->keys[i], key) == 0) return 1;
}
return 0;
}
Value map_keys_array(const Value *vm) {
if (!vm || vm->type != VAL_MAP || !vm->map) return make_array_from_values(NULL, 0);
Map *m = (Map*)vm->map;
if (m->count <= 0) return make_array_from_values(NULL, 0);
Value *tmp = (Value*)malloc(sizeof(Value) * m->count);
for (int i = 0; i < m->count; ++i) {
tmp[i] = make_string(m->keys[i]);
}
Value arr = make_array_from_values(tmp, m->count);
for (int i = 0; i < m->count; ++i) free_value(tmp[i]);
free(tmp);
return arr;
}
Value map_values_array(const Value *vm) {
if (!vm || vm->type != VAL_MAP || !vm->map) return make_array_from_values(NULL, 0);
Map *m = (Map*)vm->map;
if (m->count <= 0) return make_array_from_values(NULL, 0);
Value *tmp = (Value*)malloc(sizeof(Value) * m->count);
for (int i = 0; i < m->count; ++i) {
tmp[i] = copy_value(&m->vals[i]);
}
Value arr = make_array_from_values(tmp, m->count);
for (int i = 0; i < m->count; ++i) free_value(tmp[i]);
free(tmp);
return arr;
}
Value make_array_from_values(const Value *vals, int count) {
if (count < 0) count = 0;
@ -499,140 +410,5 @@ int value_equals(const Value *a, const Value *b) {
}
}
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;
}

1097
src/vm.c

File diff suppressed because it is too large Load diff

9
src/vm_case_abs.inc Normal file
View file

@ -0,0 +1,9 @@
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;
}

39
src/vm_case_add.inc Normal file
View file

@ -0,0 +1,39 @@
case OP_ADD: {
Value b = pop_value(vm);
Value a = pop_value(vm);
if (a.type == VAL_INT && b.type == VAL_INT) {
Value res = make_int(a.i + b.i);
free_value(a);
free_value(b);
push_value(vm, res);
} else if (a.type == VAL_STRING && b.type == VAL_STRING) {
const char *sa = a.s ? a.s : "";
const char *sb = b.s ? b.s : "";
size_t la = strlen(sa);
size_t lb = strlen(sb);
char *buf = (char*)malloc(la + lb + 1);
if (!buf) {
fprintf(stderr, "Runtime error: out of memory during string concatenation\n");
exit(1);
}
memcpy(buf, sa, la);
memcpy(buf + la, sb, lb);
buf[la + lb] = '\0';
Value res;
res.type = VAL_STRING;
res.s = buf;
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, both strings, or both arrays, got %s and %s\n",
value_type_name(a.type), value_type_name(b.type));
exit(1);
}
break;
}

9
src/vm_case_and.inc Normal file
View file

@ -0,0 +1,9 @@
case OP_AND: {
Value b = pop_value(vm);
Value a = pop_value(vm);
int res = value_is_truthy(&a) && value_is_truthy(&b);
free_value(a);
free_value(b);
push_value(vm, make_int(res));
break;
}

View file

@ -0,0 +1,18 @@
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;
}

15
src/vm_case_arr_pop.inc Normal file
View file

@ -0,0 +1,15 @@
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;
}

16
src/vm_case_arr_push.inc Normal file
View file

@ -0,0 +1,16 @@
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;
}

View file

@ -0,0 +1,17 @@
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;
}

19
src/vm_case_arr_set.inc Normal file
View file

@ -0,0 +1,19 @@
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;
}

25
src/vm_case_call.inc Normal file
View file

@ -0,0 +1,25 @@
case OP_CALL: {
int argc = inst.operand;
if (argc < 0) argc = 0;
/* collect args in reverse (preserve order) */
Value *args = NULL;
if (argc > 0) {
args = (Value*)malloc(sizeof(Value) * argc);
/* pop args into array in reverse */
for (int i = argc - 1; i >= 0; --i) {
args[i] = pop_value(vm);
}
}
/* pop function value */
Value fnv = pop_value(vm);
if (fnv.type != VAL_FUNCTION) {
fprintf(stderr, "Runtime type error: CALL expects function\n");
exit(1);
}
/* push new frame and transfer args */
vm_push_frame(vm, fnv.fn, argc, args);
/* free args array (locals moved), free fnv (no-op for function) */
free(args);
/* note: fnv contains a pointer to the Bytecode, don't free here */
break;
}

12
src/vm_case_clamp.inc Normal file
View file

@ -0,0 +1,12 @@
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;
}

11
src/vm_case_clear.inc Normal file
View file

@ -0,0 +1,11 @@
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;
}

13
src/vm_case_contains.inc Normal file
View file

@ -0,0 +1,13 @@
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;
}

18
src/vm_case_div.inc Normal file
View file

@ -0,0 +1,18 @@
case OP_DIV: {
Value b = pop_value(vm);
Value a = pop_value(vm);
if (a.type != VAL_INT || b.type != VAL_INT) {
fprintf(stderr, "Runtime type error: DIV expects ints, got %s and %s\n",
value_type_name(a.type), value_type_name(b.type));
exit(1);
}
if (b.i == 0) {
fprintf(stderr, "Runtime error: division by zero\n");
exit(1);
}
Value res = make_int(a.i / b.i);
free_value(a);
free_value(b);
push_value(vm, res);
break;
}

9
src/vm_case_dup.inc Normal file
View file

@ -0,0 +1,9 @@
case OP_DUP: {
if (vm->sp < 0) {
fprintf(stderr, "Runtime error: stack underflow for DUP\n");
exit(1);
}
Value top = vm->stack[vm->sp];
push_value(vm, copy_value(&top));
break;
}

11
src/vm_case_enumerate.inc Normal file
View file

@ -0,0 +1,11 @@
case OP_ENUMERATE: {
Value arr = pop_value(vm);
if (arr.type != VAL_ARRAY) {
fprintf(stderr, "Runtime type error: ENUMERATE expects array\n");
exit(1);
}
Value out = bi_enumerate(&arr);
free_value(arr);
push_value(vm, out);
break;
}

19
src/vm_case_eq.inc Normal file
View file

@ -0,0 +1,19 @@
case OP_EQ: {
Value b = pop_value(vm);
Value a = pop_value(vm);
int eq = 0;
if (a.type == b.type) {
switch (a.type) {
case VAL_INT: eq = (a.i == b.i); break;
case VAL_STRING: eq = (a.s && b.s) ? (strcmp(a.s, b.s) == 0) : (a.s == b.s); break;
case VAL_FUNCTION: eq = (a.fn == b.fn); break;
case VAL_NIL: eq = 1; break;
default: eq = 0; break;
}
} else {
eq = 0;
}
push_value(vm, make_int(eq ? 1 : 0));
free_value(a); free_value(b);
break;
}

13
src/vm_case_find.inc Normal file
View file

@ -0,0 +1,13 @@
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 = bi_find(&hay, &needle);
free_value(hay);
free_value(needle);
push_value(vm, make_int(idx));
break;
}

11
src/vm_case_gt.inc Normal file
View file

@ -0,0 +1,11 @@
case OP_GT: {
Value b = pop_value(vm);
Value a = pop_value(vm);
if (a.type != VAL_INT || b.type != VAL_INT) {
fprintf(stderr, "Runtime type error: GT expects ints\n");
exit(1);
}
push_value(vm, make_int(a.i > b.i ? 1 : 0));
free_value(a); free_value(b);
break;
}

11
src/vm_case_gte.inc Normal file
View file

@ -0,0 +1,11 @@
case OP_GTE: {
Value b = pop_value(vm);
Value a = pop_value(vm);
if (a.type != VAL_INT || b.type != VAL_INT) {
fprintf(stderr, "Runtime type error: GTE expects ints\n");
exit(1);
}
push_value(vm, make_int(a.i >= b.i ? 1 : 0));
free_value(a); free_value(b);
break;
}

2
src/vm_case_halt.inc Normal file
View file

@ -0,0 +1,2 @@
case OP_HALT:
return;

9
src/vm_case_has_key.inc Normal file
View file

@ -0,0 +1,9 @@
case OP_HAS_KEY: {
Value key = pop_value(vm);
Value m = pop_value(vm);
if (m.type != VAL_MAP || key.type != VAL_STRING) { fprintf(stderr, "HAS_KEY expects (map, string)\n"); exit(1); }
int ok = map_has(&m, key.s ? key.s : "");
free_value(m); free_value(key);
push_value(vm, make_int(ok ? 1 : 0));
break;
}

28
src/vm_case_index_get.inc Normal file
View file

@ -0,0 +1,28 @@
case OP_INDEX_GET: {
Value idx = pop_value(vm);
Value container = pop_value(vm);
if (container.type == VAL_ARRAY) {
if (idx.type != VAL_INT) { fprintf(stderr, "INDEX_GET index must be int for array\n"); exit(1); }
Value elem;
if (!array_get_copy(&container, (int)idx.i, &elem)) {
fprintf(stderr, "Runtime error: index out of range\n"); exit(1);
}
free_value(container);
free_value(idx);
push_value(vm, elem);
} else if (container.type == VAL_MAP) {
if (idx.type != VAL_STRING) { fprintf(stderr, "INDEX_GET key must be string for map\n"); exit(1); }
Value out;
if (!map_get_copy(&container, idx.s ? idx.s : "", &out)) {
/* missing -> nil */
out = make_nil();
}
free_value(container);
free_value(idx);
push_value(vm, out);
} else {
fprintf(stderr, "Runtime type error: INDEX_GET expects array or map\n");
exit(1);
}
break;
}

13
src/vm_case_index_of.inc Normal file
View file

@ -0,0 +1,13 @@
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;
}

25
src/vm_case_index_set.inc Normal file
View file

@ -0,0 +1,25 @@
case OP_INDEX_SET: {
Value v = pop_value(vm);
Value idx = pop_value(vm);
Value container = pop_value(vm);
if (container.type == VAL_ARRAY) {
if (idx.type != VAL_INT) { fprintf(stderr, "INDEX_SET index must be int for array\n"); exit(1); }
if (!array_set(&container, (int)idx.i, v)) {
fprintf(stderr, "Runtime error: index out of range\n"); exit(1);
}
/* v moved into array */
free_value(container);
free_value(idx);
} else if (container.type == VAL_MAP) {
if (idx.type != VAL_STRING) { fprintf(stderr, "INDEX_SET key must be string for map\n"); exit(1); }
if (!map_set(&container, idx.s ? idx.s : "", v)) {
fprintf(stderr, "Runtime error: map set failed\n"); exit(1);
}
free_value(container);
free_value(idx);
} else {
fprintf(stderr, "Runtime type error: INDEX_SET expects array or map\n");
exit(1);
}
break;
}

13
src/vm_case_join.inc Normal file
View file

@ -0,0 +1,13 @@
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);
}
Value out = bi_join(&arr, &sep);
free_value(arr);
free_value(sep);
push_value(vm, out);
break;
}

4
src/vm_case_jump.inc Normal file
View file

@ -0,0 +1,4 @@
case OP_JUMP: {
f->ip = inst.operand;
break;
}

View file

@ -0,0 +1,9 @@
case OP_JUMP_IF_FALSE: {
Value cond = pop_value(vm);
int truthy = value_is_truthy(&cond);
free_value(cond);
if (!truthy) {
f->ip = inst.operand;
}
break;
}

8
src/vm_case_keys.inc Normal file
View file

@ -0,0 +1,8 @@
case OP_KEYS: {
Value m = pop_value(vm);
if (m.type != VAL_MAP) { fprintf(stderr, "KEYS expects map\n"); exit(1); }
Value arr = map_keys_array(&m);
free_value(m);
push_value(vm, arr);
break;
}

16
src/vm_case_len.inc Normal file
View file

@ -0,0 +1,16 @@
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;
}

View file

@ -0,0 +1,10 @@
case OP_LOAD_CONST: {
int idx = inst.operand;
if (idx < 0 || idx >= f->fn->const_count) {
fprintf(stderr, "Runtime error: constant index out of range\n");
exit(1);
}
Value c = copy_value(&f->fn->constants[idx]);
push_value(vm, c);
break;
}

View file

@ -0,0 +1,9 @@
case OP_LOAD_GLOBAL: {
int idx = inst.operand;
if (idx < 0 || idx >= VM_MAX_GLOBALS) {
fprintf(stderr, "Runtime error: global index out of range\n");
exit(1);
}
push_value(vm, copy_value(&vm->globals[idx]));
break;
}

View file

@ -0,0 +1,10 @@
case OP_LOAD_LOCAL: {
int slot = inst.operand;
if (slot < 0 || slot >= FRAME_MAX_LOCALS) {
fprintf(stderr, "Runtime error: local slot out of range\n");
exit(1);
}
Value val = copy_value(&f->locals[slot]);
push_value(vm, val);
break;
}

14
src/vm_case_lt.inc Normal file
View file

@ -0,0 +1,14 @@
case OP_LT: {
Value b = pop_value(vm);
Value a = pop_value(vm);
if (a.type != VAL_INT || b.type != VAL_INT) {
fprintf(stderr, "Runtime type error: LT expects ints, got %s and %s\n",
value_type_name(a.type), value_type_name(b.type));
exit(1);
}
Value res = make_int(a.i < b.i ? 1 : 0);
free_value(a);
free_value(b);
push_value(vm, res);
break;
}

13
src/vm_case_lte.inc Normal file
View file

@ -0,0 +1,13 @@
case OP_LTE: {
Value b = pop_value(vm);
Value a = pop_value(vm);
if (a.type != VAL_INT || b.type != VAL_INT) {
fprintf(stderr, "Runtime type error: LTE expects ints\n");
exit(1);
}
Value res = make_int(a.i <= b.i ? 1 : 0);
free_value(a);
free_value(b);
push_value(vm, res);
break;
}

View file

@ -0,0 +1,21 @@
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;
}

17
src/vm_case_make_map.inc Normal file
View file

@ -0,0 +1,17 @@
case OP_MAKE_MAP: {
int pairs = inst.operand;
if (pairs < 0) { fprintf(stderr, "MAKE_MAP invalid pair count\n"); exit(1); }
Value m = make_map_empty();
for (int i = 0; i < pairs; ++i) {
Value val = pop_value(vm);
Value key = pop_value(vm);
if (key.type != VAL_STRING) { fprintf(stderr, "Map literal keys must be strings\n"); exit(1); }
if (!map_set(&m, key.s ? key.s : "", val)) {
fprintf(stderr, "Map literal set failed\n"); exit(1);
}
free_value(key);
/* val ownership moved */
}
push_value(vm, m);
break;
}

8
src/vm_case_max.inc Normal file
View file

@ -0,0 +1,8 @@
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;
}

8
src/vm_case_min.inc Normal file
View file

@ -0,0 +1,8 @@
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;
}

18
src/vm_case_mod.inc Normal file
View file

@ -0,0 +1,18 @@
case OP_MOD: {
Value b = pop_value(vm);
Value a = pop_value(vm);
if (a.type != VAL_INT || b.type != VAL_INT) {
fprintf(stderr, "Runtime type error: MOD expects ints, got %s and %s\n",
value_type_name(a.type), value_type_name(b.type));
exit(1);
}
if (b.i == 0) {
fprintf(stderr, "Runtime error: modulo by zero\n");
exit(1);
}
Value res = make_int(a.i % b.i);
free_value(a);
free_value(b);
push_value(vm, res);
break;
}

14
src/vm_case_mul.inc Normal file
View file

@ -0,0 +1,14 @@
case OP_MUL: {
Value b = pop_value(vm);
Value a = pop_value(vm);
if (a.type != VAL_INT || b.type != VAL_INT) {
fprintf(stderr, "Runtime type error: MUL expects ints, got %s and %s\n",
value_type_name(a.type), value_type_name(b.type));
exit(1);
}
Value res = make_int(a.i * b.i);
free_value(a);
free_value(b);
push_value(vm, res);
break;
}

19
src/vm_case_neq.inc Normal file
View file

@ -0,0 +1,19 @@
case OP_NEQ: {
Value b = pop_value(vm);
Value a = pop_value(vm);
int neq = 1;
if (a.type == b.type) {
switch (a.type) {
case VAL_INT: neq = (a.i != b.i); break;
case VAL_STRING: neq = (a.s && b.s) ? (strcmp(a.s, b.s) != 0) : (a.s != b.s); break;
case VAL_FUNCTION: neq = (a.fn != b.fn); break;
case VAL_NIL: neq = 0; break;
default: neq = 1; break;
}
} else {
neq = 1;
}
push_value(vm, make_int(neq ? 1 : 0));
free_value(a); free_value(b);
break;
}

2
src/vm_case_nop.inc Normal file
View file

@ -0,0 +1,2 @@
case OP_NOP:
break;

7
src/vm_case_not.inc Normal file
View file

@ -0,0 +1,7 @@
case OP_NOT: {
Value v = pop_value(vm);
int res = !value_is_truthy(&v);
free_value(v);
push_value(vm, make_int(res));
break;
}

9
src/vm_case_or.inc Normal file
View file

@ -0,0 +1,9 @@
case OP_OR: {
Value b = pop_value(vm);
Value a = pop_value(vm);
int res = value_is_truthy(&a) || value_is_truthy(&b);
free_value(a);
free_value(b);
push_value(vm, make_int(res));
break;
}

9
src/vm_case_pop.inc Normal file
View file

@ -0,0 +1,9 @@
case OP_POP: {
if (vm->sp < 0) {
fprintf(stderr, "Runtime error: stack underflow for POP\n");
exit(1);
}
Value v = pop_value(vm);
free_value(v); // free the popped value
break;
}

19
src/vm_case_pow.inc Normal file
View file

@ -0,0 +1,19 @@
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;
}

14
src/vm_case_print.inc Normal file
View file

@ -0,0 +1,14 @@
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++] = snap; // store snapshot
} else {
free_value(snap); // prevent leak
fprintf(stderr, "Runtime error: output buffer overflow\n");
exit(1);
}
break;
}

View file

@ -0,0 +1,12 @@
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;
}

View file

@ -0,0 +1,8 @@
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;
}

21
src/vm_case_read_file.inc Normal file
View file

@ -0,0 +1,21 @@
case OP_READ_FILE: {
Value path = pop_value(vm);
if (path.type != VAL_STRING) { fprintf(stderr, "READ_FILE expects string\n"); exit(1); }
const char *p = path.s ? path.s : "";
FILE *f = fopen(p, "rb");
if (!f) { free_value(path); push_value(vm, make_string("")); break; }
if (fseek(f, 0, SEEK_END) != 0) { fclose(f); free_value(path); push_value(vm, make_string("")); break; }
long sz = ftell(f);
if (sz < 0) { fclose(f); free_value(path); push_value(vm, make_string("")); break; }
rewind(f);
char *buf = (char*)malloc((size_t)sz + 1);
size_t n = buf ? fread(buf, 1, (size_t)sz, f) : 0;
fclose(f);
if (!buf) { free_value(path); push_value(vm, make_string("")); break; }
buf[n] = '\0';
Value out = make_string(buf);
free(buf);
free_value(path);
push_value(vm, out);
break;
}

13
src/vm_case_return.inc Normal file
View file

@ -0,0 +1,13 @@
case OP_RETURN: {
Value retv;
/* if there's something on the stack -> return it, else nil */
if (vm->sp >= 0) retv = pop_value(vm);
else retv = make_nil();
/* pop frame (free locals) */
vm_pop_frame(vm);
/* push return value into caller frame (or onto stack if no caller) */
push_value(vm, retv);
break;
}

15
src/vm_case_slice.inc Normal file
View file

@ -0,0 +1,15 @@
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;
}

13
src/vm_case_split.inc Normal file
View file

@ -0,0 +1,13 @@
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 out = bi_split(&str, &sep);
free_value(str);
free_value(sep);
push_value(vm, out);
break;
}

View file

@ -0,0 +1,11 @@
case OP_STORE_GLOBAL: {
int idx = inst.operand;
if (idx < 0 || idx >= VM_MAX_GLOBALS) {
fprintf(stderr, "Runtime error: global index out of range\n");
exit(1);
}
Value v = pop_value(vm);
free_value(vm->globals[idx]);
vm->globals[idx] = v;
break;
}

View file

@ -0,0 +1,12 @@
case OP_STORE_LOCAL: {
int slot = inst.operand;
if (slot < 0 || slot >= FRAME_MAX_LOCALS) {
fprintf(stderr, "Runtime error: local slot out of range\n");
exit(1);
}
Value v = pop_value(vm);
/* free previous local then move v into it */
free_value(f->locals[slot]);
f->locals[slot] = v;
break;
}

14
src/vm_case_sub.inc Normal file
View file

@ -0,0 +1,14 @@
case OP_SUB: {
Value b = pop_value(vm);
Value a = pop_value(vm);
if (a.type != VAL_INT || b.type != VAL_INT) {
fprintf(stderr, "Runtime type error: SUB expects ints, got %s and %s\n",
value_type_name(a.type), value_type_name(b.type));
exit(1);
}
Value res = make_int(a.i - b.i);
free_value(a);
free_value(b);
push_value(vm, res);
break;
}

15
src/vm_case_substr.inc Normal file
View file

@ -0,0 +1,15 @@
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);
}
Value out = bi_substr(&str, (int)startv.i, (int)lenv.i);
free_value(str);
free_value(startv);
free_value(lenv);
push_value(vm, out);
break;
}

11
src/vm_case_swap.inc Normal file
View file

@ -0,0 +1,11 @@
case OP_SWAP: {
if (vm->sp < 1) {
fprintf(stderr, "Runtime error: stack underflow for SWAP\n");
exit(1);
}
Value a = vm->stack[vm->sp];
Value b = vm->stack[vm->sp - 1];
vm->stack[vm->sp] = b;
vm->stack[vm->sp - 1] = a;
break;
}

30
src/vm_case_to_number.inc Normal file
View file

@ -0,0 +1,30 @@
case OP_TO_NUMBER: {
Value v = pop_value(vm);
if (v.type == VAL_INT) {
/* pass-through */
Value out = make_int(v.i);
free_value(v);
push_value(vm, out);
} else if (v.type == VAL_STRING) {
const char *s = v.s ? v.s : "";
/* trim spaces */
const char *p = s;
while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p++;
char *endp = NULL;
long long parsed = strtoll(p, &endp, 10);
/* skip trailing spaces */
while (endp && (*endp == ' ' || *endp == '\t' || *endp == '\r' || *endp == '\n')) endp++;
if (endp && *endp != '\0') {
/* non-numeric suffix -> 0 */
push_value(vm, make_int(0));
} else {
push_value(vm, make_int((int64_t)parsed));
}
free_value(v);
} else {
/* nil, array, function -> 0 */
free_value(v);
push_value(vm, make_int(0));
}
break;
}

View file

@ -0,0 +1,9 @@
case OP_TO_STRING: {
Value v = pop_value(vm);
char *s = value_to_string_alloc(&v);
Value out = make_string(s ? s : "");
if (s) free(s);
free_value(v);
push_value(vm, out);
break;
}

8
src/vm_case_values.inc Normal file
View file

@ -0,0 +1,8 @@
case OP_VALUES: {
Value m = pop_value(vm);
if (m.type != VAL_MAP) { fprintf(stderr, "VALUES expects map\n"); exit(1); }
Value arr = map_values_array(&m);
free_value(m);
push_value(vm, arr);
break;
}

View file

@ -0,0 +1,17 @@
case OP_WRITE_FILE: {
Value data = pop_value(vm);
Value path = pop_value(vm);
if (path.type != VAL_STRING || data.type != VAL_STRING) { fprintf(stderr, "WRITE_FILE expects (string, string)\n"); exit(1); }
const char *p = path.s ? path.s : "";
FILE *f = fopen(p, "wb");
int ok = 0;
if (f) {
size_t len = data.s ? strlen(data.s) : 0;
ok = (fwrite(data.s ? data.s : "", 1, len, f) == len);
fclose(f);
}
free_value(path);
free_value(data);
push_value(vm, make_int(ok ? 1 : 0));
break;
}

13
src/vm_case_zip.inc Normal file
View file

@ -0,0 +1,13 @@
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);
}
Value out = bi_zip(&a, &b);
free_value(a);
free_value(b);
push_value(vm, out);
break;
}