From 13919ad87d4797e5792dbc853813086245e9a6e5 Mon Sep 17 00:00:00 2001 From: hanez Date: Mon, 15 Sep 2025 06:31:32 +0200 Subject: [PATCH] Tons of refactoring and some fixes and enhancements... ;) --- CMakeLists.txt | 2 +- Makefile | 11 + README_DEV.md | 14 + examples/arrays_iter.fun | 0 examples/builtins_conversions.fun | 0 examples/nested_loops.fun | 0 scripts/check_op_includes.py | 98 +++ scripts/run_examples.sh | 69 ++ src/array_utils.c | 49 ++ src/builtins_io.c | 31 + src/builtins_iter.c | 51 ++ src/builtins_math.c | 37 + src/builtins_string.c | 32 + src/map.c | 107 +++ src/parser.c | 165 +---- src/parser_utils.c | 172 +++++ src/str_utils.c | 125 ++++ src/value.c | 232 +----- src/vm.c | 1097 +++-------------------------- src/vm_case_abs.inc | 9 + src/vm_case_add.inc | 39 + src/vm_case_and.inc | 9 + src/vm_case_arr_insert.inc | 18 + src/vm_case_arr_pop.inc | 15 + src/vm_case_arr_push.inc | 16 + src/vm_case_arr_remove.inc | 17 + src/vm_case_arr_set.inc | 19 + src/vm_case_call.inc | 25 + src/vm_case_clamp.inc | 12 + src/vm_case_clear.inc | 11 + src/vm_case_contains.inc | 13 + src/vm_case_div.inc | 18 + src/vm_case_dup.inc | 9 + src/vm_case_enumerate.inc | 11 + src/vm_case_eq.inc | 19 + src/vm_case_find.inc | 13 + src/vm_case_gt.inc | 11 + src/vm_case_gte.inc | 11 + src/vm_case_halt.inc | 2 + src/vm_case_has_key.inc | 9 + src/vm_case_index_get.inc | 28 + src/vm_case_index_of.inc | 13 + src/vm_case_index_set.inc | 25 + src/vm_case_join.inc | 13 + src/vm_case_jump.inc | 4 + src/vm_case_jump_if_false.inc | 9 + src/vm_case_keys.inc | 8 + src/vm_case_len.inc | 16 + src/vm_case_load_const.inc | 10 + src/vm_case_load_global.inc | 9 + src/vm_case_load_local.inc | 10 + src/vm_case_lt.inc | 14 + src/vm_case_lte.inc | 13 + src/vm_case_make_array.inc | 21 + src/vm_case_make_map.inc | 17 + src/vm_case_max.inc | 8 + src/vm_case_min.inc | 8 + src/vm_case_mod.inc | 18 + src/vm_case_mul.inc | 14 + src/vm_case_neq.inc | 19 + src/vm_case_nop.inc | 2 + src/vm_case_not.inc | 7 + src/vm_case_or.inc | 9 + src/vm_case_pop.inc | 9 + src/vm_case_pow.inc | 19 + src/vm_case_print.inc | 14 + src/vm_case_random_int.inc | 12 + src/vm_case_random_seed.inc | 8 + src/vm_case_read_file.inc | 21 + src/vm_case_return.inc | 13 + src/vm_case_slice.inc | 15 + src/vm_case_split.inc | 13 + src/vm_case_store_global.inc | 11 + src/vm_case_store_local.inc | 12 + src/vm_case_sub.inc | 14 + src/vm_case_substr.inc | 15 + src/vm_case_swap.inc | 11 + src/vm_case_to_number.inc | 30 + src/vm_case_to_string.inc | 9 + src/vm_case_values.inc | 8 + src/vm_case_write_file.inc | 17 + src/vm_case_zip.inc | 13 + 82 files changed, 1775 insertions(+), 1382 deletions(-) create mode 100644 README_DEV.md mode change 100644 => 100755 examples/arrays_iter.fun mode change 100644 => 100755 examples/builtins_conversions.fun mode change 100644 => 100755 examples/nested_loops.fun create mode 100755 scripts/check_op_includes.py create mode 100755 scripts/run_examples.sh create mode 100644 src/array_utils.c create mode 100644 src/builtins_io.c create mode 100644 src/builtins_iter.c create mode 100644 src/builtins_math.c create mode 100644 src/builtins_string.c create mode 100644 src/map.c create mode 100644 src/parser_utils.c create mode 100644 src/str_utils.c create mode 100644 src/vm_case_abs.inc create mode 100644 src/vm_case_add.inc create mode 100644 src/vm_case_and.inc create mode 100644 src/vm_case_arr_insert.inc create mode 100644 src/vm_case_arr_pop.inc create mode 100644 src/vm_case_arr_push.inc create mode 100644 src/vm_case_arr_remove.inc create mode 100644 src/vm_case_arr_set.inc create mode 100644 src/vm_case_call.inc create mode 100644 src/vm_case_clamp.inc create mode 100644 src/vm_case_clear.inc create mode 100644 src/vm_case_contains.inc create mode 100644 src/vm_case_div.inc create mode 100644 src/vm_case_dup.inc create mode 100644 src/vm_case_enumerate.inc create mode 100644 src/vm_case_eq.inc create mode 100644 src/vm_case_find.inc create mode 100644 src/vm_case_gt.inc create mode 100644 src/vm_case_gte.inc create mode 100644 src/vm_case_halt.inc create mode 100644 src/vm_case_has_key.inc create mode 100644 src/vm_case_index_get.inc create mode 100644 src/vm_case_index_of.inc create mode 100644 src/vm_case_index_set.inc create mode 100644 src/vm_case_join.inc create mode 100644 src/vm_case_jump.inc create mode 100644 src/vm_case_jump_if_false.inc create mode 100644 src/vm_case_keys.inc create mode 100644 src/vm_case_len.inc create mode 100644 src/vm_case_load_const.inc create mode 100644 src/vm_case_load_global.inc create mode 100644 src/vm_case_load_local.inc create mode 100644 src/vm_case_lt.inc create mode 100644 src/vm_case_lte.inc create mode 100644 src/vm_case_make_array.inc create mode 100644 src/vm_case_make_map.inc create mode 100644 src/vm_case_max.inc create mode 100644 src/vm_case_min.inc create mode 100644 src/vm_case_mod.inc create mode 100644 src/vm_case_mul.inc create mode 100644 src/vm_case_neq.inc create mode 100644 src/vm_case_nop.inc create mode 100644 src/vm_case_not.inc create mode 100644 src/vm_case_or.inc create mode 100644 src/vm_case_pop.inc create mode 100644 src/vm_case_pow.inc create mode 100644 src/vm_case_print.inc create mode 100644 src/vm_case_random_int.inc create mode 100644 src/vm_case_random_seed.inc create mode 100644 src/vm_case_read_file.inc create mode 100644 src/vm_case_return.inc create mode 100644 src/vm_case_slice.inc create mode 100644 src/vm_case_split.inc create mode 100644 src/vm_case_store_global.inc create mode 100644 src/vm_case_store_local.inc create mode 100644 src/vm_case_sub.inc create mode 100644 src/vm_case_substr.inc create mode 100644 src/vm_case_swap.inc create mode 100644 src/vm_case_to_number.inc create mode 100644 src/vm_case_to_string.inc create mode 100644 src/vm_case_values.inc create mode 100644 src/vm_case_write_file.inc create mode 100644 src/vm_case_zip.inc diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b791a5..8231649 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/Makefile b/Makefile index 66550b3..8cf6fa2 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/README_DEV.md b/README_DEV.md new file mode 100644 index 0000000..b066be8 --- /dev/null +++ b/README_DEV.md @@ -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: diff --git a/examples/arrays_iter.fun b/examples/arrays_iter.fun old mode 100644 new mode 100755 diff --git a/examples/builtins_conversions.fun b/examples/builtins_conversions.fun old mode 100644 new mode 100755 diff --git a/examples/nested_loops.fun b/examples/nested_loops.fun old mode 100644 new mode 100755 diff --git a/scripts/check_op_includes.py b/scripts/check_op_includes.py new file mode 100755 index 0000000..d560177 --- /dev/null +++ b/scripts/check_op_includes.py @@ -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()) diff --git a/scripts/run_examples.sh b/scripts/run_examples.sh new file mode 100755 index 0000000..347e0aa --- /dev/null +++ b/scripts/run_examples.sh @@ -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 diff --git a/src/array_utils.c b/src/array_utils.c new file mode 100644 index 0000000..656127a --- /dev/null +++ b/src/array_utils.c @@ -0,0 +1,49 @@ +#include "value.h" +#include + +/* 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); + } +} diff --git a/src/builtins_io.c b/src/builtins_io.c new file mode 100644 index 0000000..1f096f9 --- /dev/null +++ b/src/builtins_io.c @@ -0,0 +1,31 @@ +#include +#include +#include + +/* 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; +} diff --git a/src/builtins_iter.c b/src/builtins_iter.c new file mode 100644 index 0000000..f988097 --- /dev/null +++ b/src/builtins_iter.c @@ -0,0 +1,51 @@ +#include "value.h" +#include + +/* 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; +} diff --git a/src/builtins_math.c b/src/builtins_math.c new file mode 100644 index 0000000..ca36ab9 --- /dev/null +++ b/src/builtins_math.c @@ -0,0 +1,37 @@ +#include +#include + +/* 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); +} diff --git a/src/builtins_string.c b/src/builtins_string.c new file mode 100644 index 0000000..7031d39 --- /dev/null +++ b/src/builtins_string.c @@ -0,0 +1,32 @@ +#include "value.h" +#include + +/* 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); +} diff --git a/src/map.c b/src/map.c new file mode 100644 index 0000000..6e2f45f --- /dev/null +++ b/src/map.c @@ -0,0 +1,107 @@ +#include "value.h" +#include +#include + +/* 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; +} diff --git a/src/parser.c b/src/parser.c index 2038785..4d09d72 100644 --- a/src/parser.c +++ b/src/parser.c @@ -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 { diff --git a/src/parser_utils.c b/src/parser_utils.c new file mode 100644 index 0000000..0a74e0b --- /dev/null +++ b/src/parser_utils.c @@ -0,0 +1,172 @@ +#include +#include +#include +#include +#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; +} diff --git a/src/str_utils.c b/src/str_utils.c new file mode 100644 index 0000000..b05b675 --- /dev/null +++ b/src/str_utils.c @@ -0,0 +1,125 @@ +#include "value.h" +#include +#include + +/* 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; +} diff --git a/src/value.c b/src/value.c index 19fff47..1c13bf1 100644 --- a/src/value.c +++ b/src/value.c @@ -3,6 +3,10 @@ #include #include +/* 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; -} diff --git a/src/vm.c b/src/vm.c index c08c68d..7bc7480 100644 --- a/src/vm.c +++ b/src/vm.c @@ -5,6 +5,51 @@ #include #include +/* Bring in split-out built-ins without changing the build system yet */ +#include "builtins_math.c" +#include "builtins_io.c" +#include "map.c" +#include "builtins_string.c" +#include "builtins_iter.c" + +/* +Opcode case include index (vm_case_*.inc): +- Core/stack/frame: + vm_case_nop.inc, vm_case_halt.inc, + vm_case_load_const.inc, vm_case_load_local.inc, vm_case_store_local.inc, + vm_case_load_global.inc, vm_case_store_global.inc, + vm_case_pop.inc, vm_case_dup.inc, vm_case_swap.inc, + vm_case_call.inc, vm_case_return.inc, vm_case_print.inc, vm_case_jump.inc, vm_case_jump_if_false.inc +- Arithmetic and logic: + vm_case_add.inc, vm_case_sub.inc, vm_case_mul.inc, vm_case_div.inc, + vm_case_mod.inc, vm_case_lt.inc, vm_case_lte.inc, vm_case_gt.inc, vm_case_gte.inc, + vm_case_eq.inc, vm_case_neq.inc, vm_case_and.inc, vm_case_or.inc, vm_case_not.inc +- Conversions: + vm_case_to_number.inc, vm_case_to_string.inc +- Arrays and slices: + vm_case_make_array.inc, vm_case_len.inc, + vm_case_index_get.inc, vm_case_index_set.inc, + vm_case_arr_push.inc, vm_case_arr_pop.inc, vm_case_arr_set.inc, vm_case_arr_insert.inc, vm_case_arr_remove.inc, + vm_case_slice.inc +- Strings and iteration helpers: + vm_case_split.inc, vm_case_join.inc, vm_case_substr.inc, vm_case_find.inc, + vm_case_enumerate.inc, vm_case_zip.inc +- Maps and I/O: + vm_case_make_map.inc, vm_case_keys.inc, vm_case_values.inc, vm_case_has_key.inc, + vm_case_read_file.inc, vm_case_write_file.inc +- Math utils / RNG: + vm_case_min.inc, vm_case_max.inc, vm_case_clamp.inc, vm_case_abs.inc, vm_case_pow.inc, + vm_case_random_seed.inc, vm_case_random_int.inc + +Dev tips: +- When adding a new opcode: + 1) Define OP_ in bytecode.h and opcode_names[] in vm.h. + 2) Implement its VM handler in src/vm_case_.inc. + 3) Include it in the switch below. + 4) Run scripts/check_op_includes.py to verify coverage. +- You can run scripts/run_examples.sh to sanity-check examples quickly. +*/ + static const char* value_type_name(ValueType t) { switch (t) { case VAL_INT: return "int"; @@ -147,995 +192,69 @@ void vm_run(VM *vm, Bytecode *entry) { vm->instr_count++; /* count each executed instruction */ switch (inst.op) { - case OP_NOP: - break; - - 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; - } - - 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; - } - - 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; - } - - 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; - } - - 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; - } - - 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; - } - - 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; - } - - 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; - } - - 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; - } - - case OP_JUMP: { - f->ip = inst.operand; - break; - } - - 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; - } - - 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; - } - - 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; - } - - 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; - } - - - 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; - } - - 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; - } - - 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; - } - - 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; - } - - case OP_HALT: - return; - - 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; - } - - 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; - } - - 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; - } - - case OP_MAKE_ARRAY: { - int n = inst.operand; - if (n < 0 || vm->sp + 1 < n) { - fprintf(stderr, "Runtime error: invalid element count for MAKE_ARRAY\n"); - exit(1); - } - /* pop n values into temp array preserving original order */ - Value *vals = (Value*)malloc(sizeof(Value) * n); - if (!vals) { fprintf(stderr, "Runtime error: OOM in MAKE_ARRAY\n"); exit(1); } - for (int i = n - 1; i >= 0; --i) { - vals[i] = pop_value(vm); /* take ownership */ - } - /* build array by copying values, then free originals */ - Value arr = make_array_from_values(vals, n); - for (int i = 0; i < n; ++i) { - free_value(vals[i]); - } - free(vals); - push_value(vm, arr); - break; - } - - case OP_INDEX_GET: { - Value idx = pop_value(vm); - Value 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; - } - - 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; - } - - 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; - } - - 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; - } - - 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; - } - - 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; - } - - 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; - } - - 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; - } - - 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; - } - - 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; - } - - 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; - } - - 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_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; - } - - 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; - } - - 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; - } - - 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; - } - - 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; - } - - 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; - } - - 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; - } - - 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; - } - - 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; - } - - 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; - } - - 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; - } - - case OP_NOT: { - Value v = pop_value(vm); - int res = !value_is_truthy(&v); - free_value(v); - push_value(vm, make_int(res)); - break; - } - + #include "vm_case_nop.inc" + #include "vm_case_load_const.inc" + #include "vm_case_load_local.inc" + #include "vm_case_store_local.inc" + #include "vm_case_add.inc" + #include "vm_case_sub.inc" + #include "vm_case_mul.inc" + #include "vm_case_div.inc" + #include "vm_case_lt.inc" + #include "vm_case_lte.inc" + #include "vm_case_jump.inc" + #include "vm_case_gt.inc" + #include "vm_case_gte.inc" + #include "vm_case_eq.inc" + #include "vm_case_neq.inc" + #include "vm_case_jump_if_false.inc" + #include "vm_case_call.inc" + #include "vm_case_return.inc" + #include "vm_case_print.inc" + #include "vm_case_halt.inc" + #include "vm_case_pop.inc" + #include "vm_case_dup.inc" + #include "vm_case_swap.inc" + #include "vm_case_make_array.inc" + #include "vm_case_index_get.inc" + #include "vm_case_index_set.inc" + #include "vm_case_len.inc" + #include "vm_case_arr_push.inc" + #include "vm_case_arr_pop.inc" + #include "vm_case_arr_set.inc" + #include "vm_case_arr_insert.inc" + #include "vm_case_arr_remove.inc" + #include "vm_case_slice.inc" + #include "vm_case_to_number.inc" + #include "vm_case_to_string.inc" + #include "vm_case_split.inc" + #include "vm_case_join.inc" + #include "vm_case_substr.inc" + #include "vm_case_find.inc" + #include "vm_case_contains.inc" + #include "vm_case_index_of.inc" + #include "vm_case_clear.inc" + #include "vm_case_enumerate.inc" + #include "vm_case_zip.inc" + #include "vm_case_min.inc" + #include "vm_case_max.inc" + #include "vm_case_clamp.inc" + #include "vm_case_abs.inc" + #include "vm_case_pow.inc" + #include "vm_case_random_seed.inc" + #include "vm_case_random_int.inc" + #include "vm_case_make_map.inc" + #include "vm_case_keys.inc" + #include "vm_case_values.inc" + #include "vm_case_has_key.inc" + #include "vm_case_read_file.inc" + #include "vm_case_write_file.inc" + #include "vm_case_load_global.inc" + #include "vm_case_store_global.inc" + #include "vm_case_mod.inc" + #include "vm_case_and.inc" + #include "vm_case_or.inc" + #include "vm_case_not.inc" default: if (!opcode_is_valid(inst.op)) { fprintf(stderr, "Runtime error: unknown opcode %d (%s) at instruction %d\n", diff --git a/src/vm_case_abs.inc b/src/vm_case_abs.inc new file mode 100644 index 0000000..9ae9d49 --- /dev/null +++ b/src/vm_case_abs.inc @@ -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; +} diff --git a/src/vm_case_add.inc b/src/vm_case_add.inc new file mode 100644 index 0000000..20e187c --- /dev/null +++ b/src/vm_case_add.inc @@ -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; +} diff --git a/src/vm_case_and.inc b/src/vm_case_and.inc new file mode 100644 index 0000000..98ed4e8 --- /dev/null +++ b/src/vm_case_and.inc @@ -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; +} diff --git a/src/vm_case_arr_insert.inc b/src/vm_case_arr_insert.inc new file mode 100644 index 0000000..8f2b91f --- /dev/null +++ b/src/vm_case_arr_insert.inc @@ -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; +} diff --git a/src/vm_case_arr_pop.inc b/src/vm_case_arr_pop.inc new file mode 100644 index 0000000..d746891 --- /dev/null +++ b/src/vm_case_arr_pop.inc @@ -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; +} diff --git a/src/vm_case_arr_push.inc b/src/vm_case_arr_push.inc new file mode 100644 index 0000000..c2b86ed --- /dev/null +++ b/src/vm_case_arr_push.inc @@ -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; +} diff --git a/src/vm_case_arr_remove.inc b/src/vm_case_arr_remove.inc new file mode 100644 index 0000000..ca56230 --- /dev/null +++ b/src/vm_case_arr_remove.inc @@ -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; +} diff --git a/src/vm_case_arr_set.inc b/src/vm_case_arr_set.inc new file mode 100644 index 0000000..7c3d3d6 --- /dev/null +++ b/src/vm_case_arr_set.inc @@ -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; +} diff --git a/src/vm_case_call.inc b/src/vm_case_call.inc new file mode 100644 index 0000000..1b0ccbe --- /dev/null +++ b/src/vm_case_call.inc @@ -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; +} diff --git a/src/vm_case_clamp.inc b/src/vm_case_clamp.inc new file mode 100644 index 0000000..cdca05e --- /dev/null +++ b/src/vm_case_clamp.inc @@ -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; +} diff --git a/src/vm_case_clear.inc b/src/vm_case_clear.inc new file mode 100644 index 0000000..7fede5f --- /dev/null +++ b/src/vm_case_clear.inc @@ -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; +} diff --git a/src/vm_case_contains.inc b/src/vm_case_contains.inc new file mode 100644 index 0000000..c21e096 --- /dev/null +++ b/src/vm_case_contains.inc @@ -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; +} diff --git a/src/vm_case_div.inc b/src/vm_case_div.inc new file mode 100644 index 0000000..aa47ebe --- /dev/null +++ b/src/vm_case_div.inc @@ -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; +} diff --git a/src/vm_case_dup.inc b/src/vm_case_dup.inc new file mode 100644 index 0000000..1cbb7eb --- /dev/null +++ b/src/vm_case_dup.inc @@ -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; +} diff --git a/src/vm_case_enumerate.inc b/src/vm_case_enumerate.inc new file mode 100644 index 0000000..67ac4b3 --- /dev/null +++ b/src/vm_case_enumerate.inc @@ -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; +} diff --git a/src/vm_case_eq.inc b/src/vm_case_eq.inc new file mode 100644 index 0000000..9a559bd --- /dev/null +++ b/src/vm_case_eq.inc @@ -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; +} diff --git a/src/vm_case_find.inc b/src/vm_case_find.inc new file mode 100644 index 0000000..502b09c --- /dev/null +++ b/src/vm_case_find.inc @@ -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; +} diff --git a/src/vm_case_gt.inc b/src/vm_case_gt.inc new file mode 100644 index 0000000..dd444fb --- /dev/null +++ b/src/vm_case_gt.inc @@ -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; +} diff --git a/src/vm_case_gte.inc b/src/vm_case_gte.inc new file mode 100644 index 0000000..2ac9550 --- /dev/null +++ b/src/vm_case_gte.inc @@ -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; +} diff --git a/src/vm_case_halt.inc b/src/vm_case_halt.inc new file mode 100644 index 0000000..90c0a1d --- /dev/null +++ b/src/vm_case_halt.inc @@ -0,0 +1,2 @@ +case OP_HALT: + return; diff --git a/src/vm_case_has_key.inc b/src/vm_case_has_key.inc new file mode 100644 index 0000000..9f59267 --- /dev/null +++ b/src/vm_case_has_key.inc @@ -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; +} diff --git a/src/vm_case_index_get.inc b/src/vm_case_index_get.inc new file mode 100644 index 0000000..0d7f5f8 --- /dev/null +++ b/src/vm_case_index_get.inc @@ -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; +} diff --git a/src/vm_case_index_of.inc b/src/vm_case_index_of.inc new file mode 100644 index 0000000..643c065 --- /dev/null +++ b/src/vm_case_index_of.inc @@ -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; +} diff --git a/src/vm_case_index_set.inc b/src/vm_case_index_set.inc new file mode 100644 index 0000000..2da30b4 --- /dev/null +++ b/src/vm_case_index_set.inc @@ -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; +} diff --git a/src/vm_case_join.inc b/src/vm_case_join.inc new file mode 100644 index 0000000..bf9556c --- /dev/null +++ b/src/vm_case_join.inc @@ -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; +} diff --git a/src/vm_case_jump.inc b/src/vm_case_jump.inc new file mode 100644 index 0000000..0fed573 --- /dev/null +++ b/src/vm_case_jump.inc @@ -0,0 +1,4 @@ +case OP_JUMP: { + f->ip = inst.operand; + break; +} diff --git a/src/vm_case_jump_if_false.inc b/src/vm_case_jump_if_false.inc new file mode 100644 index 0000000..173b962 --- /dev/null +++ b/src/vm_case_jump_if_false.inc @@ -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; +} diff --git a/src/vm_case_keys.inc b/src/vm_case_keys.inc new file mode 100644 index 0000000..e8524bb --- /dev/null +++ b/src/vm_case_keys.inc @@ -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; +} diff --git a/src/vm_case_len.inc b/src/vm_case_len.inc new file mode 100644 index 0000000..b95c3ae --- /dev/null +++ b/src/vm_case_len.inc @@ -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; +} diff --git a/src/vm_case_load_const.inc b/src/vm_case_load_const.inc new file mode 100644 index 0000000..1d5f384 --- /dev/null +++ b/src/vm_case_load_const.inc @@ -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; +} diff --git a/src/vm_case_load_global.inc b/src/vm_case_load_global.inc new file mode 100644 index 0000000..07749c0 --- /dev/null +++ b/src/vm_case_load_global.inc @@ -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; +} diff --git a/src/vm_case_load_local.inc b/src/vm_case_load_local.inc new file mode 100644 index 0000000..56e191a --- /dev/null +++ b/src/vm_case_load_local.inc @@ -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; +} diff --git a/src/vm_case_lt.inc b/src/vm_case_lt.inc new file mode 100644 index 0000000..86eb0cc --- /dev/null +++ b/src/vm_case_lt.inc @@ -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; +} diff --git a/src/vm_case_lte.inc b/src/vm_case_lte.inc new file mode 100644 index 0000000..41e9e51 --- /dev/null +++ b/src/vm_case_lte.inc @@ -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; +} diff --git a/src/vm_case_make_array.inc b/src/vm_case_make_array.inc new file mode 100644 index 0000000..bead699 --- /dev/null +++ b/src/vm_case_make_array.inc @@ -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; +} diff --git a/src/vm_case_make_map.inc b/src/vm_case_make_map.inc new file mode 100644 index 0000000..ca79961 --- /dev/null +++ b/src/vm_case_make_map.inc @@ -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; +} diff --git a/src/vm_case_max.inc b/src/vm_case_max.inc new file mode 100644 index 0000000..1fd8b00 --- /dev/null +++ b/src/vm_case_max.inc @@ -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; +} diff --git a/src/vm_case_min.inc b/src/vm_case_min.inc new file mode 100644 index 0000000..fc221e6 --- /dev/null +++ b/src/vm_case_min.inc @@ -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; +} diff --git a/src/vm_case_mod.inc b/src/vm_case_mod.inc new file mode 100644 index 0000000..24f7a26 --- /dev/null +++ b/src/vm_case_mod.inc @@ -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; +} diff --git a/src/vm_case_mul.inc b/src/vm_case_mul.inc new file mode 100644 index 0000000..679d1fe --- /dev/null +++ b/src/vm_case_mul.inc @@ -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; +} diff --git a/src/vm_case_neq.inc b/src/vm_case_neq.inc new file mode 100644 index 0000000..11e2d2f --- /dev/null +++ b/src/vm_case_neq.inc @@ -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; +} diff --git a/src/vm_case_nop.inc b/src/vm_case_nop.inc new file mode 100644 index 0000000..5e2723c --- /dev/null +++ b/src/vm_case_nop.inc @@ -0,0 +1,2 @@ +case OP_NOP: + break; diff --git a/src/vm_case_not.inc b/src/vm_case_not.inc new file mode 100644 index 0000000..7d97f32 --- /dev/null +++ b/src/vm_case_not.inc @@ -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; +} diff --git a/src/vm_case_or.inc b/src/vm_case_or.inc new file mode 100644 index 0000000..b146695 --- /dev/null +++ b/src/vm_case_or.inc @@ -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; +} diff --git a/src/vm_case_pop.inc b/src/vm_case_pop.inc new file mode 100644 index 0000000..5ef72d4 --- /dev/null +++ b/src/vm_case_pop.inc @@ -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; +} diff --git a/src/vm_case_pow.inc b/src/vm_case_pow.inc new file mode 100644 index 0000000..ed38f29 --- /dev/null +++ b/src/vm_case_pow.inc @@ -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; +} diff --git a/src/vm_case_print.inc b/src/vm_case_print.inc new file mode 100644 index 0000000..6bd64f8 --- /dev/null +++ b/src/vm_case_print.inc @@ -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; +} diff --git a/src/vm_case_random_int.inc b/src/vm_case_random_int.inc new file mode 100644 index 0000000..24c0d72 --- /dev/null +++ b/src/vm_case_random_int.inc @@ -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; +} diff --git a/src/vm_case_random_seed.inc b/src/vm_case_random_seed.inc new file mode 100644 index 0000000..e7c50d6 --- /dev/null +++ b/src/vm_case_random_seed.inc @@ -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; +} diff --git a/src/vm_case_read_file.inc b/src/vm_case_read_file.inc new file mode 100644 index 0000000..251b286 --- /dev/null +++ b/src/vm_case_read_file.inc @@ -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; +} diff --git a/src/vm_case_return.inc b/src/vm_case_return.inc new file mode 100644 index 0000000..264564c --- /dev/null +++ b/src/vm_case_return.inc @@ -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; +} diff --git a/src/vm_case_slice.inc b/src/vm_case_slice.inc new file mode 100644 index 0000000..406aed4 --- /dev/null +++ b/src/vm_case_slice.inc @@ -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; +} diff --git a/src/vm_case_split.inc b/src/vm_case_split.inc new file mode 100644 index 0000000..4d844ab --- /dev/null +++ b/src/vm_case_split.inc @@ -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; +} diff --git a/src/vm_case_store_global.inc b/src/vm_case_store_global.inc new file mode 100644 index 0000000..6b8a18c --- /dev/null +++ b/src/vm_case_store_global.inc @@ -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; +} diff --git a/src/vm_case_store_local.inc b/src/vm_case_store_local.inc new file mode 100644 index 0000000..70aeab0 --- /dev/null +++ b/src/vm_case_store_local.inc @@ -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; +} diff --git a/src/vm_case_sub.inc b/src/vm_case_sub.inc new file mode 100644 index 0000000..de655f9 --- /dev/null +++ b/src/vm_case_sub.inc @@ -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; +} diff --git a/src/vm_case_substr.inc b/src/vm_case_substr.inc new file mode 100644 index 0000000..ec1b591 --- /dev/null +++ b/src/vm_case_substr.inc @@ -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; +} diff --git a/src/vm_case_swap.inc b/src/vm_case_swap.inc new file mode 100644 index 0000000..7102ce9 --- /dev/null +++ b/src/vm_case_swap.inc @@ -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; +} diff --git a/src/vm_case_to_number.inc b/src/vm_case_to_number.inc new file mode 100644 index 0000000..35f3a55 --- /dev/null +++ b/src/vm_case_to_number.inc @@ -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; +} diff --git a/src/vm_case_to_string.inc b/src/vm_case_to_string.inc new file mode 100644 index 0000000..0ded744 --- /dev/null +++ b/src/vm_case_to_string.inc @@ -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; +} diff --git a/src/vm_case_values.inc b/src/vm_case_values.inc new file mode 100644 index 0000000..d151f8f --- /dev/null +++ b/src/vm_case_values.inc @@ -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; +} diff --git a/src/vm_case_write_file.inc b/src/vm_case_write_file.inc new file mode 100644 index 0000000..2669cde --- /dev/null +++ b/src/vm_case_write_file.inc @@ -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; +} diff --git a/src/vm_case_zip.inc b/src/vm_case_zip.inc new file mode 100644 index 0000000..6d62ece --- /dev/null +++ b/src/vm_case_zip.inc @@ -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; +}