Tons of refactoring and some fixes and enhancements... ;)
This commit is contained in:
parent
d1bbd2e501
commit
13919ad87d
82 changed files with 1775 additions and 1382 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
cmake_minimum_required(VERSION 3.16)
|
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 11)
|
||||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
|
|
||||||
11
Makefile
11
Makefile
|
|
@ -48,3 +48,14 @@ repl: fun
|
||||||
# Convenience: run a script (usage: make run SCRIPT=examples/strings_test.fun)
|
# Convenience: run a script (usage: make run SCRIPT=examples/strings_test.fun)
|
||||||
run: fun
|
run: fun
|
||||||
./$(BUILD_DIR)/fun $(SCRIPT)
|
./$(BUILD_DIR)/fun $(SCRIPT)
|
||||||
|
|
||||||
|
# Additional convenience targets
|
||||||
|
.PHONY: verify-ops examples run-examples
|
||||||
|
|
||||||
|
# Verify that each OP_* in bytecode.h has a corresponding vm_case_*.inc include in vm.c
|
||||||
|
verify-ops:
|
||||||
|
@./scripts/check_op_includes.py --verbose
|
||||||
|
|
||||||
|
# Build and run all examples (searches for the fun binary automatically)
|
||||||
|
examples run-examples: fun
|
||||||
|
@./scripts/run_examples.sh
|
||||||
|
|
|
||||||
14
README_DEV.md
Normal file
14
README_DEV.md
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Developer Notes
|
||||||
|
|
||||||
|
This project splits VM opcode handlers into one-include-per-op file for readability.
|
||||||
|
The main interpreter loop (`src/vm.c`, `vm_run`) includes `vm_case_*.inc` files,
|
||||||
|
each containing a single `case OP_*: { ... }` handler.
|
||||||
|
|
||||||
|
## Adding a new opcode
|
||||||
|
|
||||||
|
1. Add the enum in `src/bytecode.h` (e.g., `OP_FOO`) and add its human-readable name
|
||||||
|
to `opcode_names[]` in `src/vm.h`. Update `opcode_is_valid` upper bound if needed.
|
||||||
|
2. Implement the VM handler in `src/vm_case_foo.inc` using the established style:
|
||||||
|
- No function wrappers, just `case OP_FOO: { ... break; }`.
|
||||||
|
3. Include the new file in the switch inside `src/vm.c` (`vm_run`), near similar ops.
|
||||||
|
4. Run the checker script:
|
||||||
0
examples/arrays_iter.fun
Normal file → Executable file
0
examples/arrays_iter.fun
Normal file → Executable file
0
examples/builtins_conversions.fun
Normal file → Executable file
0
examples/builtins_conversions.fun
Normal file → Executable file
0
examples/nested_loops.fun
Normal file → Executable file
0
examples/nested_loops.fun
Normal file → Executable file
98
scripts/check_op_includes.py
Executable file
98
scripts/check_op_includes.py
Executable file
|
|
@ -0,0 +1,98 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
BYTECODE = ROOT / "src" / "bytecode.h"
|
||||||
|
VM_C = ROOT / "src" / "vm.c"
|
||||||
|
|
||||||
|
OP_RE = re.compile(r'\bOP_([A-Z0-9_]+)\b')
|
||||||
|
INCLUDE_RE = re.compile(r'#include\s+"vm_case_([a-z0-9_]+)\.inc"')
|
||||||
|
|
||||||
|
def read_text(path: Path) -> str:
|
||||||
|
try:
|
||||||
|
return path.read_text(encoding="utf-8")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"error: cannot read {path}: {e}", file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
def parse_opcodes_from_bytecode(text: str) -> set[str]:
|
||||||
|
# Gather OP_* from the enum definition, excluding duplicates in comments
|
||||||
|
ops = set()
|
||||||
|
# crude: find typedef enum block and extract OP_* tokens
|
||||||
|
enum_start = text.find("typedef enum")
|
||||||
|
if enum_start == -1:
|
||||||
|
enum_start = 0
|
||||||
|
enum_block = text[enum_start:]
|
||||||
|
for m in OP_RE.finditer(enum_block):
|
||||||
|
token = m.group(1)
|
||||||
|
ops.add(token)
|
||||||
|
return ops
|
||||||
|
|
||||||
|
def parse_includes_from_vm(text: str) -> set[str]:
|
||||||
|
incs = set(m.group(1) for m in INCLUDE_RE.finditer(text))
|
||||||
|
# Convert include base names back to uppercase opcode tokens heuristically.
|
||||||
|
mapping = {
|
||||||
|
"nop":"NOP","halt":"HALT",
|
||||||
|
"load_const":"LOAD_CONST","load_local":"LOAD_LOCAL","store_local":"STORE_LOCAL",
|
||||||
|
"load_global":"LOAD_GLOBAL","store_global":"STORE_GLOBAL",
|
||||||
|
"pop":"POP","dup":"DUP","swap":"SWAP",
|
||||||
|
"call":"CALL","return":"RETURN","print":"PRINT","jump":"JUMP","jump_if_false":"JUMP_IF_FALSE",
|
||||||
|
"add":"ADD","sub":"SUB","mul":"MUL","div":"DIV",
|
||||||
|
"mod":"MOD","lt":"LT","lte":"LTE","gt":"GT","gte":"GTE",
|
||||||
|
"eq":"EQ","neq":"NEQ","and":"AND","or":"OR","not":"NOT",
|
||||||
|
"make_array":"MAKE_ARRAY","len":"LEN",
|
||||||
|
"index_get":"INDEX_GET","index_set":"INDEX_SET",
|
||||||
|
"arr_push":"ARR_PUSH","arr_pop":"ARR_POP","arr_set":"ARR_SET","arr_insert":"ARR_INSERT","arr_remove":"ARR_REMOVE",
|
||||||
|
"slice":"SLICE",
|
||||||
|
"to_number":"TO_NUMBER","to_string":"TO_STRING",
|
||||||
|
"split":"SPLIT","join":"JOIN","substr":"SUBSTR","find":"FIND",
|
||||||
|
"enumerate":"ENUMERATE","zip":"ZIP",
|
||||||
|
"min":"MIN","max":"MAX","clamp":"CLAMP","abs":"ABS","pow":"POW",
|
||||||
|
"random_seed":"RANDOM_SEED","random_int":"RANDOM_INT",
|
||||||
|
"make_map":"MAKE_MAP","keys":"KEYS","values":"VALUES","has_key":"HAS_KEY",
|
||||||
|
"read_file":"READ_FILE","write_file":"WRITE_FILE",
|
||||||
|
}
|
||||||
|
tokens = set()
|
||||||
|
for inc in incs:
|
||||||
|
tokens.add(mapping.get(inc, inc.upper()))
|
||||||
|
return tokens
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
ap = argparse.ArgumentParser(description="Check that vm_case_*.inc includes cover OP_* opcodes.")
|
||||||
|
ap.add_argument("-v", "--verbose", action="store_true", help="print summary of ops and includes")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
bc_text = read_text(BYTECODE)
|
||||||
|
vm_text = read_text(VM_C)
|
||||||
|
|
||||||
|
ops = parse_opcodes_from_bytecode(bc_text)
|
||||||
|
inc_tokens = parse_includes_from_vm(vm_text)
|
||||||
|
|
||||||
|
missing_handlers = sorted([op for op in ops if op not in inc_tokens])
|
||||||
|
extra_includes = sorted([t for t in inc_tokens if t not in ops])
|
||||||
|
|
||||||
|
ok = True
|
||||||
|
if missing_handlers:
|
||||||
|
ok = False
|
||||||
|
print("Missing vm_case includes for opcodes:", ", ".join(missing_handlers))
|
||||||
|
if extra_includes:
|
||||||
|
ok = False
|
||||||
|
print("Includes without matching OP_* in bytecode.h:", ", ".join(extra_includes))
|
||||||
|
|
||||||
|
if ok:
|
||||||
|
if args.verbose:
|
||||||
|
print(f"OK: vm_case includes cover OP_* set from bytecode.h")
|
||||||
|
print(f" OP_* count : {len(ops)}")
|
||||||
|
print(f" includes count : {len(inc_tokens)}")
|
||||||
|
print(f" OP_* tokens : {', '.join(sorted(ops))}")
|
||||||
|
print(f" include tokens : {', '.join(sorted(inc_tokens))}")
|
||||||
|
else:
|
||||||
|
print("OK: vm_case includes cover OP_* set from bytecode.h")
|
||||||
|
return 0
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
69
scripts/run_examples.sh
Executable file
69
scripts/run_examples.sh
Executable file
|
|
@ -0,0 +1,69 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")"/.. && pwd)"
|
||||||
|
EX_DIR="$ROOT/examples"
|
||||||
|
|
||||||
|
# Allow override via first arg or FUN_BIN env
|
||||||
|
BIN="${1:-${FUN_BIN:-}}"
|
||||||
|
|
||||||
|
pick_bin() {
|
||||||
|
local cands=(
|
||||||
|
"$ROOT/fun"
|
||||||
|
"$ROOT/build/fun"
|
||||||
|
"$ROOT/build/bin/fun"
|
||||||
|
"$ROOT/cmake-build-debug/fun"
|
||||||
|
"$ROOT/cmake-build-release/fun"
|
||||||
|
)
|
||||||
|
for p in "${cands[@]}"; do
|
||||||
|
[[ -x "$p" ]] && { echo "$p"; return 0; }
|
||||||
|
done
|
||||||
|
# Last resort: search shallowly for an executable named 'fun' under common build dirs
|
||||||
|
local found
|
||||||
|
found="$(
|
||||||
|
find "$ROOT" -maxdepth 3 -type f -name fun -perm -u+x 2>/dev/null \
|
||||||
|
| grep -E '/(build|cmake-build[^/]*)/fun$' \
|
||||||
|
| head -n 1 || true
|
||||||
|
)"
|
||||||
|
[[ -n "$found" ]] && { echo "$found"; return 0; }
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -z "${BIN}" ]]; then
|
||||||
|
if ! BIN="$(pick_bin)"; then
|
||||||
|
echo "error: fun binary not found. Try building it (e.g., via CMake) or pass it explicitly:" >&2
|
||||||
|
echo " scripts/run_examples.sh /path/to/fun" >&2
|
||||||
|
echo "or set FUN_BIN=/path/to/fun" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -x "$BIN" ]]; then
|
||||||
|
echo "error: fun binary not found or not executable at $BIN" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
shopt -s nullglob
|
||||||
|
files=("$EX_DIR"/*.fun)
|
||||||
|
shopt -u nullglob
|
||||||
|
|
||||||
|
if (( ${#files[@]} == 0 )); then
|
||||||
|
echo "No .fun example files found in $EX_DIR"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
rc=0
|
||||||
|
for f in "${files[@]}"; do
|
||||||
|
echo "=== Running: ${f#$ROOT/} ==="
|
||||||
|
if ! "$BIN" "$f"; then
|
||||||
|
echo "FAILED: ${f#$ROOT/}"
|
||||||
|
rc=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( rc == 0 )); then
|
||||||
|
echo "All examples ran successfully."
|
||||||
|
else
|
||||||
|
echo "Some examples failed."
|
||||||
|
fi
|
||||||
|
exit $rc
|
||||||
49
src/array_utils.c
Normal file
49
src/array_utils.c
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
#include "value.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* array utilities */
|
||||||
|
|
||||||
|
int array_contains(const Value *v, const Value *needle) {
|
||||||
|
if (!v || v->type != VAL_ARRAY || !v->arr) return 0;
|
||||||
|
int n = array_length(v);
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
Value item;
|
||||||
|
if (array_get_copy(v, i, &item)) {
|
||||||
|
int eq = value_equals(&item, needle);
|
||||||
|
free_value(item);
|
||||||
|
if (eq) return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int array_index_of(const Value *v, const Value *needle) {
|
||||||
|
if (!v || v->type != VAL_ARRAY || !v->arr) return -1;
|
||||||
|
int n = array_length(v);
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
Value item;
|
||||||
|
if (array_get_copy(v, i, &item)) {
|
||||||
|
int eq = value_equals(&item, needle);
|
||||||
|
free_value(item);
|
||||||
|
if (eq) return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void array_clear(Value *v) {
|
||||||
|
if (!v || v->type != VAL_ARRAY || !v->arr) return;
|
||||||
|
int n = array_length(v);
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
Value item;
|
||||||
|
if (array_get_copy(v, i, &item)) {
|
||||||
|
free_value(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* internal clear uses public API to reset to zero length */
|
||||||
|
/* Since we don't expose capacity, emulate by popping elements */
|
||||||
|
Value out;
|
||||||
|
while (array_pop(v, &out)) {
|
||||||
|
free_value(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/builtins_io.c
Normal file
31
src/builtins_io.c
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* File I/O helpers: return malloc'd buffers or status ints */
|
||||||
|
|
||||||
|
char *bio_read_file_strdup(const char *path) {
|
||||||
|
if (!path) return strdup("");
|
||||||
|
FILE *f = fopen(path, "rb");
|
||||||
|
if (!f) return strdup("");
|
||||||
|
if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return strdup(""); }
|
||||||
|
long sz = ftell(f);
|
||||||
|
if (sz < 0) { fclose(f); return strdup(""); }
|
||||||
|
rewind(f);
|
||||||
|
char *buf = (char*)malloc((size_t)sz + 1);
|
||||||
|
if (!buf) { fclose(f); return strdup(""); }
|
||||||
|
size_t n = fread(buf, 1, (size_t)sz, f);
|
||||||
|
fclose(f);
|
||||||
|
buf[n] = '\0';
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bio_write_file(const char *path, const char *data, size_t len) {
|
||||||
|
if (!path) return 0;
|
||||||
|
FILE *f = fopen(path, "wb");
|
||||||
|
if (!f) return 0;
|
||||||
|
if (!data) data = "";
|
||||||
|
int ok = (fwrite(data, 1, len, f) == len);
|
||||||
|
fclose(f);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
51
src/builtins_iter.c
Normal file
51
src/builtins_iter.c
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
#include "value.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
/* enumerate(arr) -> [[0, v0], [1, v1], ...] */
|
||||||
|
Value bi_enumerate(const Value *arr) {
|
||||||
|
int n = array_length(arr);
|
||||||
|
if (n <= 0) return make_array_from_values(NULL, 0);
|
||||||
|
Value *pairs = (Value*)malloc(sizeof(Value) * n);
|
||||||
|
if (!pairs) return make_array_from_values(NULL, 0);
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
Value elem;
|
||||||
|
array_get_copy(arr, i, &elem);
|
||||||
|
Value kv_vals[2];
|
||||||
|
kv_vals[0] = make_int(i);
|
||||||
|
kv_vals[1] = elem;
|
||||||
|
Value kv = make_array_from_values(kv_vals, 2);
|
||||||
|
free_value(kv_vals[0]);
|
||||||
|
free_value(kv_vals[1]);
|
||||||
|
pairs[i] = kv;
|
||||||
|
}
|
||||||
|
Value out = make_array_from_values(pairs, n);
|
||||||
|
for (int i = 0; i < n; ++i) free_value(pairs[i]);
|
||||||
|
free(pairs);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* zip(a, b) -> [[a0,b0], [a1,b1], ...] up to min(len(a),len(b)) */
|
||||||
|
Value bi_zip(const Value *a, const Value *b) {
|
||||||
|
int na = array_length(a);
|
||||||
|
int nb = array_length(b);
|
||||||
|
int n = na < nb ? na : nb;
|
||||||
|
if (n <= 0) return make_array_from_values(NULL, 0);
|
||||||
|
Value *pairs = (Value*)malloc(sizeof(Value) * n);
|
||||||
|
if (!pairs) return make_array_from_values(NULL, 0);
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
Value av, bv;
|
||||||
|
array_get_copy(a, i, &av);
|
||||||
|
array_get_copy(b, i, &bv);
|
||||||
|
Value kv_vals[2];
|
||||||
|
kv_vals[0] = av;
|
||||||
|
kv_vals[1] = bv;
|
||||||
|
Value kv = make_array_from_values(kv_vals, 2);
|
||||||
|
free_value(kv_vals[0]);
|
||||||
|
free_value(kv_vals[1]);
|
||||||
|
pairs[i] = kv;
|
||||||
|
}
|
||||||
|
Value out = make_array_from_values(pairs, n);
|
||||||
|
for (int i = 0; i < n; ++i) free_value(pairs[i]);
|
||||||
|
free(pairs);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
37
src/builtins_math.c
Normal file
37
src/builtins_math.c
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
/* Math built-ins (pure helpers) */
|
||||||
|
|
||||||
|
int64_t bm_min(int64_t a, int64_t b) { return a < b ? a : b; }
|
||||||
|
int64_t bm_max(int64_t a, int64_t b) { return a > b ? a : b; }
|
||||||
|
|
||||||
|
int64_t bm_clamp(int64_t x, int64_t lo, int64_t hi) {
|
||||||
|
if (x < lo) return lo;
|
||||||
|
if (x > hi) return hi;
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t bm_abs(int64_t x) { return x < 0 ? -x : x; }
|
||||||
|
|
||||||
|
int64_t bm_pow(int64_t base, int64_t exp) {
|
||||||
|
if (exp < 0) return 0; /* integer pow: negative exponent -> 0 */
|
||||||
|
int64_t res = 1;
|
||||||
|
int64_t b = base;
|
||||||
|
int64_t e = exp;
|
||||||
|
while (e > 0) {
|
||||||
|
if (e & 1) res *= b;
|
||||||
|
b *= b;
|
||||||
|
e >>= 1;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RNG: thin wrappers over libc's srand/rand for now */
|
||||||
|
void bm_random_seed(unsigned int seed) { srand(seed); }
|
||||||
|
|
||||||
|
int64_t bm_random_int(int64_t lo, int64_t hi) {
|
||||||
|
if (hi <= lo) return lo;
|
||||||
|
int64_t span = hi - lo;
|
||||||
|
return lo + (rand() % span);
|
||||||
|
}
|
||||||
32
src/builtins_string.c
Normal file
32
src/builtins_string.c
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#include "value.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
/* String built-ins wrappers used by VM opcodes */
|
||||||
|
|
||||||
|
Value bi_split(const Value *str, const Value *sep) {
|
||||||
|
const char *s = (str && str->type == VAL_STRING && str->s) ? str->s : "";
|
||||||
|
const char *p = (sep && sep->type == VAL_STRING && sep->s) ? sep->s : "";
|
||||||
|
return string_split_to_array(s, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value bi_join(const Value *arr, const Value *sep) {
|
||||||
|
const char *p = (sep && sep->type == VAL_STRING && sep->s) ? sep->s : "";
|
||||||
|
char *s = array_join_with_sep(arr, p);
|
||||||
|
Value out = make_string(s ? s : "");
|
||||||
|
if (s) free(s);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value bi_substr(const Value *str, int start, int len) {
|
||||||
|
const char *s = (str && str->type == VAL_STRING && str->s) ? str->s : "";
|
||||||
|
char *sub = string_substr(s, start, len);
|
||||||
|
Value out = make_string(sub ? sub : "");
|
||||||
|
if (sub) free(sub);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bi_find(const Value *hay, const Value *needle) {
|
||||||
|
const char *h = (hay && hay->type == VAL_STRING && hay->s) ? hay->s : "";
|
||||||
|
const char *n = (needle && needle->type == VAL_STRING && needle->s) ? needle->s : "";
|
||||||
|
return string_find(h, n);
|
||||||
|
}
|
||||||
107
src/map.c
Normal file
107
src/map.c
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
#include "value.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* Internal Map definition; Value holds struct Map* */
|
||||||
|
typedef struct Map {
|
||||||
|
int refcount;
|
||||||
|
int count;
|
||||||
|
int cap;
|
||||||
|
char **keys; /* each key owned here */
|
||||||
|
Value *vals; /* each value owned here */
|
||||||
|
} Map;
|
||||||
|
|
||||||
|
Value make_map_empty(void) {
|
||||||
|
Map *m = (Map*)malloc(sizeof(Map));
|
||||||
|
if (!m) return make_nil();
|
||||||
|
m->refcount = 1;
|
||||||
|
m->count = 0;
|
||||||
|
m->cap = 0;
|
||||||
|
m->keys = NULL;
|
||||||
|
m->vals = NULL;
|
||||||
|
Value v;
|
||||||
|
v.type = VAL_MAP;
|
||||||
|
v.map = (struct Map*)m;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int map_ensure_cap(Map *m, int need) {
|
||||||
|
if (m->cap >= need) return 1;
|
||||||
|
int ncap = m->cap == 0 ? 4 : m->cap * 2;
|
||||||
|
while (ncap < need) ncap *= 2;
|
||||||
|
char **nkeys = (char**)realloc(m->keys, sizeof(char*) * ncap);
|
||||||
|
Value *nvals = (Value*)realloc(m->vals, sizeof(Value) * ncap);
|
||||||
|
if (!nkeys || !nvals) return 0;
|
||||||
|
m->keys = nkeys;
|
||||||
|
m->vals = nvals;
|
||||||
|
m->cap = ncap;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int map_set(Value *vm, const char *key, Value v) {
|
||||||
|
if (!vm || vm->type != VAL_MAP || !vm->map || !key) { free_value(v); return 0; }
|
||||||
|
Map *m = (Map*)vm->map;
|
||||||
|
for (int i = 0; i < m->count; ++i) {
|
||||||
|
if (strcmp(m->keys[i], key) == 0) {
|
||||||
|
free_value(m->vals[i]);
|
||||||
|
m->vals[i] = v;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!map_ensure_cap(m, m->count + 1)) { free_value(v); return 0; }
|
||||||
|
m->keys[m->count] = strdup(key);
|
||||||
|
m->vals[m->count] = v;
|
||||||
|
m->count++;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int map_get_copy(const Value *vm, const char *key, Value *out) {
|
||||||
|
if (!vm || vm->type != VAL_MAP || !vm->map || !key) return 0;
|
||||||
|
Map *m = (Map*)vm->map;
|
||||||
|
for (int i = 0; i < m->count; ++i) {
|
||||||
|
if (strcmp(m->keys[i], key) == 0) {
|
||||||
|
if (out) *out = copy_value(&m->vals[i]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int map_has(const Value *vm, const char *key) {
|
||||||
|
if (!vm || vm->type != VAL_MAP || !vm->map || !key) return 0;
|
||||||
|
Map *m = (Map*)vm->map;
|
||||||
|
for (int i = 0; i < m->count; ++i) {
|
||||||
|
if (strcmp(m->keys[i], key) == 0) return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value map_keys_array(const Value *vm) {
|
||||||
|
if (!vm || vm->type != VAL_MAP || !vm->map) return make_array_from_values(NULL, 0);
|
||||||
|
Map *m = (Map*)vm->map;
|
||||||
|
if (m->count <= 0) return make_array_from_values(NULL, 0);
|
||||||
|
Value *tmp = (Value*)malloc(sizeof(Value) * m->count);
|
||||||
|
if (!tmp) return make_array_from_values(NULL, 0);
|
||||||
|
for (int i = 0; i < m->count; ++i) {
|
||||||
|
tmp[i] = make_string(m->keys[i]);
|
||||||
|
}
|
||||||
|
Value arr = make_array_from_values(tmp, m->count);
|
||||||
|
for (int i = 0; i < m->count; ++i) free_value(tmp[i]);
|
||||||
|
free(tmp);
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value map_values_array(const Value *vm) {
|
||||||
|
if (!vm || vm->type != VAL_MAP || !vm->map) return make_array_from_values(NULL, 0);
|
||||||
|
Map *m = (Map*)vm->map;
|
||||||
|
if (m->count <= 0) return make_array_from_values(NULL, 0);
|
||||||
|
Value *tmp = (Value*)malloc(sizeof(Value) * m->count);
|
||||||
|
if (!tmp) return make_array_from_values(NULL, 0);
|
||||||
|
for (int i = 0; i < m->count; ++i) {
|
||||||
|
tmp[i] = copy_value(&m->vals[i]);
|
||||||
|
}
|
||||||
|
Value arr = make_array_from_values(tmp, m->count);
|
||||||
|
for (int i = 0; i < m->count; ++i) free_value(tmp[i]);
|
||||||
|
free(tmp);
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
165
src/parser.c
165
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) {
|
#include "parser_utils.c"
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* very small global symbol table for LOAD_GLOBAL/STORE_GLOBAL */
|
/* very small global symbol table for LOAD_GLOBAL/STORE_GLOBAL */
|
||||||
static struct {
|
static struct {
|
||||||
|
|
|
||||||
172
src/parser_utils.c
Normal file
172
src/parser_utils.c
Normal file
|
|
@ -0,0 +1,172 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include "parser.h"
|
||||||
|
#include "value.h"
|
||||||
|
#include "vm.h"
|
||||||
|
|
||||||
|
static char *read_file_all(const char *path, size_t *out_len) {
|
||||||
|
FILE *f = fopen(path, "rb");
|
||||||
|
if (!f) return NULL;
|
||||||
|
if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return NULL; }
|
||||||
|
long sz = ftell(f);
|
||||||
|
if (sz < 0) { fclose(f); return NULL; }
|
||||||
|
rewind(f);
|
||||||
|
char *buf = (char*)malloc((size_t)sz + 1);
|
||||||
|
if (!buf) { fclose(f); return NULL; }
|
||||||
|
size_t n = fread(buf, 1, (size_t)sz, f);
|
||||||
|
fclose(f);
|
||||||
|
buf[n] = '\0';
|
||||||
|
if (out_len) *out_len = n;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void skip_ws(const char *src, size_t len, size_t *pos) {
|
||||||
|
while (*pos < len) {
|
||||||
|
char c = src[*pos];
|
||||||
|
if (c == ' ' || c == '\t' || c == '\r' || c == '\n') { (*pos)++; continue; }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void skip_line(const char *src, size_t len, size_t *pos) {
|
||||||
|
while (*pos < len && src[*pos] != '\n') (*pos)++;
|
||||||
|
if (*pos < len && src[*pos] == '\n') (*pos)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void skip_comments(const char *src, size_t len, size_t *pos) {
|
||||||
|
for (;;) {
|
||||||
|
skip_ws(src, len, pos);
|
||||||
|
if (*pos + 1 < len && src[*pos] == '/' && src[*pos + 1] == '/') {
|
||||||
|
*pos += 2;
|
||||||
|
skip_line(src, len, pos);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (*pos + 1 < len && src[*pos] == '/' && src[*pos + 1] == '*') {
|
||||||
|
*pos += 2;
|
||||||
|
while (*pos + 1 < len && !(src[*pos] == '*' && src[*pos + 1] == '/')) (*pos)++;
|
||||||
|
if (*pos + 1 < len) *pos += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int starts_with(const char *src, size_t len, size_t pos, const char *kw) {
|
||||||
|
size_t klen = strlen(kw);
|
||||||
|
if (pos + klen > len) return 0;
|
||||||
|
return strncmp(src + pos, kw, klen) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void skip_shebang_if_present(const char *src, size_t len, size_t *pos) {
|
||||||
|
if (*pos == 0 && starts_with(src, len, *pos, "#!")) {
|
||||||
|
skip_line(src, len, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void skip_identifier(const char *src, size_t len, size_t *pos) {
|
||||||
|
size_t p = *pos;
|
||||||
|
if (p < len && (isalpha((unsigned char)src[p]) || src[p] == '_')) {
|
||||||
|
p++;
|
||||||
|
while (p < len && (isalnum((unsigned char)src[p]) || src[p] == '_')) p++;
|
||||||
|
}
|
||||||
|
*pos = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int consume_char(const char *src, size_t len, size_t *pos, char expected) {
|
||||||
|
skip_ws(src, len, pos);
|
||||||
|
if (*pos < len && src[*pos] == expected) { (*pos)++; return 1; }
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *parse_string_literal_any_quote(const char *src, size_t len, size_t *pos) {
|
||||||
|
skip_ws(src, len, pos);
|
||||||
|
if (*pos >= len) return NULL;
|
||||||
|
char quote = src[*pos];
|
||||||
|
if (quote != '"' && quote != '\'') return NULL;
|
||||||
|
(*pos)++; // skip opening quote
|
||||||
|
size_t cap = 64, out_len = 0;
|
||||||
|
char *out = (char*)malloc(cap);
|
||||||
|
if (!out) return NULL;
|
||||||
|
while (*pos < len) {
|
||||||
|
char c = src[*pos];
|
||||||
|
if (c == quote) { (*pos)++; break; }
|
||||||
|
if (c == '\\') {
|
||||||
|
(*pos)++;
|
||||||
|
if (*pos >= len) break;
|
||||||
|
char e = src[*pos];
|
||||||
|
switch (e) {
|
||||||
|
case 'n': c = '\n'; break;
|
||||||
|
case 'r': c = '\r'; break;
|
||||||
|
case 't': c = '\t'; break;
|
||||||
|
case '\\': c = '\\'; break;
|
||||||
|
case '"': c = '"'; break;
|
||||||
|
case '\'': c = '\''; break;
|
||||||
|
default: c = e; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (out_len + 1 >= cap) {
|
||||||
|
cap *= 2;
|
||||||
|
char *tmp = (char*)realloc(out, cap);
|
||||||
|
if (!tmp) { free(out); return NULL; }
|
||||||
|
out = tmp;
|
||||||
|
}
|
||||||
|
out[out_len++] = c;
|
||||||
|
(*pos)++;
|
||||||
|
}
|
||||||
|
if (out_len + 1 >= cap) {
|
||||||
|
char *tmp = (char*)realloc(out, cap + 1);
|
||||||
|
if (!tmp) { free(out); return NULL; }
|
||||||
|
out = tmp;
|
||||||
|
}
|
||||||
|
out[out_len] = '\0';
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Helpers for identifiers, numbers, booleans, and globals === */
|
||||||
|
|
||||||
|
static void skip_spaces(const char *src, size_t len, size_t *pos) {
|
||||||
|
while (*pos < len) {
|
||||||
|
char c = src[*pos];
|
||||||
|
if (c == ' ' || c == '\t' || c == '\r') { (*pos)++; continue; }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int read_identifier_into(const char *src, size_t len, size_t *pos, char **out_name) {
|
||||||
|
size_t p = *pos;
|
||||||
|
if (p < len && (isalpha((unsigned char)src[p]) || src[p] == '_')) {
|
||||||
|
size_t start = p;
|
||||||
|
p++;
|
||||||
|
while (p < len && (isalnum((unsigned char)src[p]) || src[p] == '_')) p++;
|
||||||
|
size_t n = p - start;
|
||||||
|
char *name = (char*)malloc(n + 1);
|
||||||
|
if (!name) return 0;
|
||||||
|
memcpy(name, src + start, n);
|
||||||
|
name[n] = '\0';
|
||||||
|
*pos = p;
|
||||||
|
*out_name = name;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t parse_int_literal_value(const char *src, size_t len, size_t *pos, int *ok) {
|
||||||
|
size_t p = *pos;
|
||||||
|
skip_spaces(src, len, &p);
|
||||||
|
int sign = 1;
|
||||||
|
if (p < len && (src[p] == '+' || src[p] == '-')) {
|
||||||
|
if (src[p] == '-') sign = -1;
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
if (p >= len || !isdigit((unsigned char)src[p])) { *ok = 0; return 0; }
|
||||||
|
int64_t val = 0;
|
||||||
|
while (p < len && isdigit((unsigned char)src[p])) {
|
||||||
|
val = val * 10 + (src[p] - '0');
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
*pos = p;
|
||||||
|
*ok = 1;
|
||||||
|
return sign * val;
|
||||||
|
}
|
||||||
125
src/str_utils.c
Normal file
125
src/str_utils.c
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
#include "value.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* string helpers returning newly allocated C strings or arrays */
|
||||||
|
|
||||||
|
char *string_substr(const char *s, int start, int len) {
|
||||||
|
if (!s) return strdup("");
|
||||||
|
int n = (int)strlen(s);
|
||||||
|
if (start < 0) start = 0;
|
||||||
|
if (start > n) start = n;
|
||||||
|
if (len < 0) len = 0;
|
||||||
|
if (start + len > n) len = n - start;
|
||||||
|
char *out = (char*)malloc((size_t)len + 1);
|
||||||
|
if (!out) return strdup("");
|
||||||
|
memcpy(out, s + start, (size_t)len);
|
||||||
|
out[len] = '\0';
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
int string_find(const char *hay, const char *needle) {
|
||||||
|
if (!hay || !needle) return -1;
|
||||||
|
const char *p = strstr(hay, needle);
|
||||||
|
if (!p) return -1;
|
||||||
|
return (int)(p - hay);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value string_split_to_array(const char *s, const char *sep) {
|
||||||
|
if (!s) s = "";
|
||||||
|
if (!sep) sep = "";
|
||||||
|
int seplen = (int)strlen(sep);
|
||||||
|
if (seplen == 0) {
|
||||||
|
/* split into characters */
|
||||||
|
int n = (int)strlen(s);
|
||||||
|
if (n <= 0) return make_array_from_values(NULL, 0);
|
||||||
|
Value *tmp = (Value*)malloc(sizeof(Value) * n);
|
||||||
|
if (!tmp) return make_array_from_values(NULL, 0);
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
char ch[2] = { s[i], 0 };
|
||||||
|
tmp[i] = make_string(ch);
|
||||||
|
}
|
||||||
|
Value arr = make_array_from_values(tmp, n);
|
||||||
|
for (int i = 0; i < n; ++i) free_value(tmp[i]);
|
||||||
|
free(tmp);
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
/* split by separator */
|
||||||
|
Value *parts = NULL;
|
||||||
|
int count = 0;
|
||||||
|
int cap = 0;
|
||||||
|
const char *cur = s;
|
||||||
|
const char *pos = NULL;
|
||||||
|
while ((pos = strstr(cur, sep)) != NULL) {
|
||||||
|
int len = (int)(pos - cur);
|
||||||
|
char *piece = (char*)malloc((size_t)len + 1);
|
||||||
|
if (!piece) break;
|
||||||
|
memcpy(piece, cur, (size_t)len);
|
||||||
|
piece[len] = '\0';
|
||||||
|
if (count >= cap) {
|
||||||
|
cap = cap == 0 ? 4 : cap * 2;
|
||||||
|
parts = (Value*)realloc(parts, sizeof(Value) * cap);
|
||||||
|
}
|
||||||
|
parts[count++] = make_string(piece);
|
||||||
|
free(piece);
|
||||||
|
cur = pos + seplen;
|
||||||
|
}
|
||||||
|
/* tail */
|
||||||
|
char *tail = strdup(cur ? cur : "");
|
||||||
|
if (count >= cap) {
|
||||||
|
cap = cap == 0 ? 1 : cap + 1;
|
||||||
|
parts = (Value*)realloc(parts, sizeof(Value) * cap);
|
||||||
|
}
|
||||||
|
parts[count++] = make_string(tail ? tail : "");
|
||||||
|
free(tail);
|
||||||
|
|
||||||
|
Value arr = make_array_from_values(parts, count);
|
||||||
|
for (int i = 0; i < count; ++i) free_value(parts[i]);
|
||||||
|
free(parts);
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *array_join_with_sep(const Value *v, const char *sep) {
|
||||||
|
if (!v || v->type != VAL_ARRAY || !v->arr) return strdup("");
|
||||||
|
if (!sep) sep = "";
|
||||||
|
/* Array is defined in value.c; we only need safe public access. */
|
||||||
|
const int n = array_length(v);
|
||||||
|
if (n <= 0) return strdup("");
|
||||||
|
|
||||||
|
char **parts = (char**)malloc(sizeof(char*) * n);
|
||||||
|
if (!parts) return strdup("");
|
||||||
|
|
||||||
|
size_t total = 0;
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
Value item;
|
||||||
|
if (!array_get_copy(v, i, &item)) {
|
||||||
|
parts[i] = strdup("");
|
||||||
|
} else {
|
||||||
|
parts[i] = value_to_string_alloc(&item);
|
||||||
|
free_value(item);
|
||||||
|
}
|
||||||
|
total += strlen(parts[i]);
|
||||||
|
if (i + 1 < n) total += strlen(sep);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *out = (char*)malloc(total + 1);
|
||||||
|
if (!out) {
|
||||||
|
for (int i = 0; i < n; ++i) free(parts[i]);
|
||||||
|
free(parts);
|
||||||
|
return strdup("");
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t off = 0;
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
size_t li = strlen(parts[i]);
|
||||||
|
memcpy(out + off, parts[i], li); off += li;
|
||||||
|
if (i + 1 < n) {
|
||||||
|
size_t ls = strlen(sep);
|
||||||
|
memcpy(out + off, sep, ls); off += ls;
|
||||||
|
}
|
||||||
|
free(parts[i]);
|
||||||
|
}
|
||||||
|
free(parts);
|
||||||
|
out[off] = '\0';
|
||||||
|
return out;
|
||||||
|
}
|
||||||
232
src/value.c
232
src/value.c
|
|
@ -3,6 +3,10 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/* Compile helper implementations into this TU to avoid build system changes */
|
||||||
|
#include "str_utils.c"
|
||||||
|
#include "array_utils.c"
|
||||||
|
|
||||||
typedef struct Array {
|
typedef struct Array {
|
||||||
int refcount;
|
int refcount;
|
||||||
int count;
|
int count;
|
||||||
|
|
@ -45,99 +49,6 @@ Value make_nil(void) {
|
||||||
return v;
|
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) {
|
Value make_array_from_values(const Value *vals, int count) {
|
||||||
if (count < 0) count = 0;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
9
src/vm_case_abs.inc
Normal file
9
src/vm_case_abs.inc
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
case OP_ABS: {
|
||||||
|
Value x = pop_value(vm);
|
||||||
|
if (x.type != VAL_INT) { fprintf(stderr, "ABS expects int\n"); exit(1); }
|
||||||
|
int64_t v = x.i;
|
||||||
|
if (v < 0) v = -v;
|
||||||
|
push_value(vm, make_int(v));
|
||||||
|
free_value(x);
|
||||||
|
break;
|
||||||
|
}
|
||||||
39
src/vm_case_add.inc
Normal file
39
src/vm_case_add.inc
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
case OP_ADD: {
|
||||||
|
Value b = pop_value(vm);
|
||||||
|
Value a = pop_value(vm);
|
||||||
|
if (a.type == VAL_INT && b.type == VAL_INT) {
|
||||||
|
Value res = make_int(a.i + b.i);
|
||||||
|
free_value(a);
|
||||||
|
free_value(b);
|
||||||
|
push_value(vm, res);
|
||||||
|
} else if (a.type == VAL_STRING && b.type == VAL_STRING) {
|
||||||
|
const char *sa = a.s ? a.s : "";
|
||||||
|
const char *sb = b.s ? b.s : "";
|
||||||
|
size_t la = strlen(sa);
|
||||||
|
size_t lb = strlen(sb);
|
||||||
|
char *buf = (char*)malloc(la + lb + 1);
|
||||||
|
if (!buf) {
|
||||||
|
fprintf(stderr, "Runtime error: out of memory during string concatenation\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
memcpy(buf, sa, la);
|
||||||
|
memcpy(buf + la, sb, lb);
|
||||||
|
buf[la + lb] = '\0';
|
||||||
|
Value res;
|
||||||
|
res.type = VAL_STRING;
|
||||||
|
res.s = buf;
|
||||||
|
free_value(a);
|
||||||
|
free_value(b);
|
||||||
|
push_value(vm, res);
|
||||||
|
} else if (a.type == VAL_ARRAY && b.type == VAL_ARRAY) {
|
||||||
|
Value res = array_concat(&a, &b);
|
||||||
|
free_value(a);
|
||||||
|
free_value(b);
|
||||||
|
push_value(vm, res);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Runtime type error: ADD expects both ints, both strings, or both arrays, got %s and %s\n",
|
||||||
|
value_type_name(a.type), value_type_name(b.type));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
9
src/vm_case_and.inc
Normal file
9
src/vm_case_and.inc
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
case OP_AND: {
|
||||||
|
Value b = pop_value(vm);
|
||||||
|
Value a = pop_value(vm);
|
||||||
|
int res = value_is_truthy(&a) && value_is_truthy(&b);
|
||||||
|
free_value(a);
|
||||||
|
free_value(b);
|
||||||
|
push_value(vm, make_int(res));
|
||||||
|
break;
|
||||||
|
}
|
||||||
18
src/vm_case_arr_insert.inc
Normal file
18
src/vm_case_arr_insert.inc
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
case OP_ARR_INSERT: {
|
||||||
|
Value v = pop_value(vm);
|
||||||
|
Value idx = pop_value(vm);
|
||||||
|
Value arr = pop_value(vm);
|
||||||
|
if (arr.type != VAL_ARRAY || idx.type != VAL_INT) {
|
||||||
|
fprintf(stderr, "Runtime type error: ARR_INSERT expects (array, int, value)\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
int n = array_insert(&arr, (int)idx.i, v);
|
||||||
|
if (n < 0) {
|
||||||
|
fprintf(stderr, "Runtime error: insert failed (OOM?)\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
free_value(arr);
|
||||||
|
free_value(idx);
|
||||||
|
push_value(vm, make_int(n));
|
||||||
|
break;
|
||||||
|
}
|
||||||
15
src/vm_case_arr_pop.inc
Normal file
15
src/vm_case_arr_pop.inc
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
case OP_ARR_POP: {
|
||||||
|
Value arr = pop_value(vm);
|
||||||
|
if (arr.type != VAL_ARRAY) {
|
||||||
|
fprintf(stderr, "Runtime type error: ARR_POP expects array\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
Value out;
|
||||||
|
if (!array_pop(&arr, &out)) {
|
||||||
|
fprintf(stderr, "Runtime error: pop from empty array\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
free_value(arr);
|
||||||
|
push_value(vm, out);
|
||||||
|
break;
|
||||||
|
}
|
||||||
16
src/vm_case_arr_push.inc
Normal file
16
src/vm_case_arr_push.inc
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
case OP_ARR_PUSH: {
|
||||||
|
Value v = pop_value(vm);
|
||||||
|
Value arr = pop_value(vm);
|
||||||
|
if (arr.type != VAL_ARRAY) {
|
||||||
|
fprintf(stderr, "Runtime type error: ARR_PUSH expects array\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
int n = array_push(&arr, v);
|
||||||
|
if (n < 0) {
|
||||||
|
fprintf(stderr, "Runtime error: push failed (OOM?)\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
free_value(arr);
|
||||||
|
push_value(vm, make_int(n));
|
||||||
|
break;
|
||||||
|
}
|
||||||
17
src/vm_case_arr_remove.inc
Normal file
17
src/vm_case_arr_remove.inc
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
case OP_ARR_REMOVE: {
|
||||||
|
Value idx = pop_value(vm);
|
||||||
|
Value arr = pop_value(vm);
|
||||||
|
if (arr.type != VAL_ARRAY || idx.type != VAL_INT) {
|
||||||
|
fprintf(stderr, "Runtime type error: ARR_REMOVE expects (array, int)\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
Value out;
|
||||||
|
if (!array_remove(&arr, (int)idx.i, &out)) {
|
||||||
|
fprintf(stderr, "Runtime error: remove index out of range\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
free_value(arr);
|
||||||
|
free_value(idx);
|
||||||
|
push_value(vm, out);
|
||||||
|
break;
|
||||||
|
}
|
||||||
19
src/vm_case_arr_set.inc
Normal file
19
src/vm_case_arr_set.inc
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
case OP_ARR_SET: {
|
||||||
|
Value v = pop_value(vm);
|
||||||
|
Value idx = pop_value(vm);
|
||||||
|
Value arr = pop_value(vm);
|
||||||
|
if (arr.type != VAL_ARRAY || idx.type != VAL_INT) {
|
||||||
|
fprintf(stderr, "Runtime type error: ARR_SET expects (array, int, value)\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (!array_set(&arr, (int)idx.i, v)) {
|
||||||
|
fprintf(stderr, "Runtime error: set index out of range\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
free_value(arr);
|
||||||
|
free_value(idx);
|
||||||
|
/* v already owned by array; push copy for return value */
|
||||||
|
push_value(vm, copy_value(&v));
|
||||||
|
free_value(v);
|
||||||
|
break;
|
||||||
|
}
|
||||||
25
src/vm_case_call.inc
Normal file
25
src/vm_case_call.inc
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
case OP_CALL: {
|
||||||
|
int argc = inst.operand;
|
||||||
|
if (argc < 0) argc = 0;
|
||||||
|
/* collect args in reverse (preserve order) */
|
||||||
|
Value *args = NULL;
|
||||||
|
if (argc > 0) {
|
||||||
|
args = (Value*)malloc(sizeof(Value) * argc);
|
||||||
|
/* pop args into array in reverse */
|
||||||
|
for (int i = argc - 1; i >= 0; --i) {
|
||||||
|
args[i] = pop_value(vm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* pop function value */
|
||||||
|
Value fnv = pop_value(vm);
|
||||||
|
if (fnv.type != VAL_FUNCTION) {
|
||||||
|
fprintf(stderr, "Runtime type error: CALL expects function\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
/* push new frame and transfer args */
|
||||||
|
vm_push_frame(vm, fnv.fn, argc, args);
|
||||||
|
/* free args array (locals moved), free fnv (no-op for function) */
|
||||||
|
free(args);
|
||||||
|
/* note: fnv contains a pointer to the Bytecode, don't free here */
|
||||||
|
break;
|
||||||
|
}
|
||||||
12
src/vm_case_clamp.inc
Normal file
12
src/vm_case_clamp.inc
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
case OP_CLAMP: {
|
||||||
|
Value hi = pop_value(vm);
|
||||||
|
Value lo = pop_value(vm);
|
||||||
|
Value x = pop_value(vm);
|
||||||
|
if (x.type != VAL_INT || lo.type != VAL_INT || hi.type != VAL_INT) { fprintf(stderr, "CLAMP expects ints\n"); exit(1); }
|
||||||
|
int64_t v = x.i;
|
||||||
|
if (v < lo.i) v = lo.i;
|
||||||
|
if (v > hi.i) v = hi.i;
|
||||||
|
push_value(vm, make_int(v));
|
||||||
|
free_value(x); free_value(lo); free_value(hi);
|
||||||
|
break;
|
||||||
|
}
|
||||||
11
src/vm_case_clear.inc
Normal file
11
src/vm_case_clear.inc
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
case OP_CLEAR: {
|
||||||
|
Value arr = pop_value(vm);
|
||||||
|
if (arr.type != VAL_ARRAY) {
|
||||||
|
fprintf(stderr, "Runtime type error: CLEAR expects array\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
array_clear(&arr);
|
||||||
|
free_value(arr);
|
||||||
|
push_value(vm, make_int(0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
13
src/vm_case_contains.inc
Normal file
13
src/vm_case_contains.inc
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
case OP_CONTAINS: {
|
||||||
|
Value needle = pop_value(vm);
|
||||||
|
Value arr = pop_value(vm);
|
||||||
|
if (arr.type != VAL_ARRAY) {
|
||||||
|
fprintf(stderr, "Runtime type error: CONTAINS expects (array, value)\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
int ok = array_contains(&arr, &needle);
|
||||||
|
free_value(arr);
|
||||||
|
free_value(needle);
|
||||||
|
push_value(vm, make_int(ok ? 1 : 0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
18
src/vm_case_div.inc
Normal file
18
src/vm_case_div.inc
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
case OP_DIV: {
|
||||||
|
Value b = pop_value(vm);
|
||||||
|
Value a = pop_value(vm);
|
||||||
|
if (a.type != VAL_INT || b.type != VAL_INT) {
|
||||||
|
fprintf(stderr, "Runtime type error: DIV expects ints, got %s and %s\n",
|
||||||
|
value_type_name(a.type), value_type_name(b.type));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (b.i == 0) {
|
||||||
|
fprintf(stderr, "Runtime error: division by zero\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
Value res = make_int(a.i / b.i);
|
||||||
|
free_value(a);
|
||||||
|
free_value(b);
|
||||||
|
push_value(vm, res);
|
||||||
|
break;
|
||||||
|
}
|
||||||
9
src/vm_case_dup.inc
Normal file
9
src/vm_case_dup.inc
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
case OP_DUP: {
|
||||||
|
if (vm->sp < 0) {
|
||||||
|
fprintf(stderr, "Runtime error: stack underflow for DUP\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
Value top = vm->stack[vm->sp];
|
||||||
|
push_value(vm, copy_value(&top));
|
||||||
|
break;
|
||||||
|
}
|
||||||
11
src/vm_case_enumerate.inc
Normal file
11
src/vm_case_enumerate.inc
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
case OP_ENUMERATE: {
|
||||||
|
Value arr = pop_value(vm);
|
||||||
|
if (arr.type != VAL_ARRAY) {
|
||||||
|
fprintf(stderr, "Runtime type error: ENUMERATE expects array\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
Value out = bi_enumerate(&arr);
|
||||||
|
free_value(arr);
|
||||||
|
push_value(vm, out);
|
||||||
|
break;
|
||||||
|
}
|
||||||
19
src/vm_case_eq.inc
Normal file
19
src/vm_case_eq.inc
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
case OP_EQ: {
|
||||||
|
Value b = pop_value(vm);
|
||||||
|
Value a = pop_value(vm);
|
||||||
|
int eq = 0;
|
||||||
|
if (a.type == b.type) {
|
||||||
|
switch (a.type) {
|
||||||
|
case VAL_INT: eq = (a.i == b.i); break;
|
||||||
|
case VAL_STRING: eq = (a.s && b.s) ? (strcmp(a.s, b.s) == 0) : (a.s == b.s); break;
|
||||||
|
case VAL_FUNCTION: eq = (a.fn == b.fn); break;
|
||||||
|
case VAL_NIL: eq = 1; break;
|
||||||
|
default: eq = 0; break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eq = 0;
|
||||||
|
}
|
||||||
|
push_value(vm, make_int(eq ? 1 : 0));
|
||||||
|
free_value(a); free_value(b);
|
||||||
|
break;
|
||||||
|
}
|
||||||
13
src/vm_case_find.inc
Normal file
13
src/vm_case_find.inc
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
case OP_FIND: {
|
||||||
|
Value needle = pop_value(vm);
|
||||||
|
Value hay = pop_value(vm);
|
||||||
|
if (hay.type != VAL_STRING || needle.type != VAL_STRING) {
|
||||||
|
fprintf(stderr, "Runtime type error: FIND expects (string, string)\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
int idx = bi_find(&hay, &needle);
|
||||||
|
free_value(hay);
|
||||||
|
free_value(needle);
|
||||||
|
push_value(vm, make_int(idx));
|
||||||
|
break;
|
||||||
|
}
|
||||||
11
src/vm_case_gt.inc
Normal file
11
src/vm_case_gt.inc
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
case OP_GT: {
|
||||||
|
Value b = pop_value(vm);
|
||||||
|
Value a = pop_value(vm);
|
||||||
|
if (a.type != VAL_INT || b.type != VAL_INT) {
|
||||||
|
fprintf(stderr, "Runtime type error: GT expects ints\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
push_value(vm, make_int(a.i > b.i ? 1 : 0));
|
||||||
|
free_value(a); free_value(b);
|
||||||
|
break;
|
||||||
|
}
|
||||||
11
src/vm_case_gte.inc
Normal file
11
src/vm_case_gte.inc
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
case OP_GTE: {
|
||||||
|
Value b = pop_value(vm);
|
||||||
|
Value a = pop_value(vm);
|
||||||
|
if (a.type != VAL_INT || b.type != VAL_INT) {
|
||||||
|
fprintf(stderr, "Runtime type error: GTE expects ints\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
push_value(vm, make_int(a.i >= b.i ? 1 : 0));
|
||||||
|
free_value(a); free_value(b);
|
||||||
|
break;
|
||||||
|
}
|
||||||
2
src/vm_case_halt.inc
Normal file
2
src/vm_case_halt.inc
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
case OP_HALT:
|
||||||
|
return;
|
||||||
9
src/vm_case_has_key.inc
Normal file
9
src/vm_case_has_key.inc
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
case OP_HAS_KEY: {
|
||||||
|
Value key = pop_value(vm);
|
||||||
|
Value m = pop_value(vm);
|
||||||
|
if (m.type != VAL_MAP || key.type != VAL_STRING) { fprintf(stderr, "HAS_KEY expects (map, string)\n"); exit(1); }
|
||||||
|
int ok = map_has(&m, key.s ? key.s : "");
|
||||||
|
free_value(m); free_value(key);
|
||||||
|
push_value(vm, make_int(ok ? 1 : 0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
28
src/vm_case_index_get.inc
Normal file
28
src/vm_case_index_get.inc
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
case OP_INDEX_GET: {
|
||||||
|
Value idx = pop_value(vm);
|
||||||
|
Value container = pop_value(vm);
|
||||||
|
if (container.type == VAL_ARRAY) {
|
||||||
|
if (idx.type != VAL_INT) { fprintf(stderr, "INDEX_GET index must be int for array\n"); exit(1); }
|
||||||
|
Value elem;
|
||||||
|
if (!array_get_copy(&container, (int)idx.i, &elem)) {
|
||||||
|
fprintf(stderr, "Runtime error: index out of range\n"); exit(1);
|
||||||
|
}
|
||||||
|
free_value(container);
|
||||||
|
free_value(idx);
|
||||||
|
push_value(vm, elem);
|
||||||
|
} else if (container.type == VAL_MAP) {
|
||||||
|
if (idx.type != VAL_STRING) { fprintf(stderr, "INDEX_GET key must be string for map\n"); exit(1); }
|
||||||
|
Value out;
|
||||||
|
if (!map_get_copy(&container, idx.s ? idx.s : "", &out)) {
|
||||||
|
/* missing -> nil */
|
||||||
|
out = make_nil();
|
||||||
|
}
|
||||||
|
free_value(container);
|
||||||
|
free_value(idx);
|
||||||
|
push_value(vm, out);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Runtime type error: INDEX_GET expects array or map\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
13
src/vm_case_index_of.inc
Normal file
13
src/vm_case_index_of.inc
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
case OP_INDEX_OF: {
|
||||||
|
Value needle = pop_value(vm);
|
||||||
|
Value arr = pop_value(vm);
|
||||||
|
if (arr.type != VAL_ARRAY) {
|
||||||
|
fprintf(stderr, "Runtime type error: INDEX_OF expects (array, value)\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
int idx = array_index_of(&arr, &needle);
|
||||||
|
free_value(arr);
|
||||||
|
free_value(needle);
|
||||||
|
push_value(vm, make_int(idx));
|
||||||
|
break;
|
||||||
|
}
|
||||||
25
src/vm_case_index_set.inc
Normal file
25
src/vm_case_index_set.inc
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
case OP_INDEX_SET: {
|
||||||
|
Value v = pop_value(vm);
|
||||||
|
Value idx = pop_value(vm);
|
||||||
|
Value container = pop_value(vm);
|
||||||
|
if (container.type == VAL_ARRAY) {
|
||||||
|
if (idx.type != VAL_INT) { fprintf(stderr, "INDEX_SET index must be int for array\n"); exit(1); }
|
||||||
|
if (!array_set(&container, (int)idx.i, v)) {
|
||||||
|
fprintf(stderr, "Runtime error: index out of range\n"); exit(1);
|
||||||
|
}
|
||||||
|
/* v moved into array */
|
||||||
|
free_value(container);
|
||||||
|
free_value(idx);
|
||||||
|
} else if (container.type == VAL_MAP) {
|
||||||
|
if (idx.type != VAL_STRING) { fprintf(stderr, "INDEX_SET key must be string for map\n"); exit(1); }
|
||||||
|
if (!map_set(&container, idx.s ? idx.s : "", v)) {
|
||||||
|
fprintf(stderr, "Runtime error: map set failed\n"); exit(1);
|
||||||
|
}
|
||||||
|
free_value(container);
|
||||||
|
free_value(idx);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Runtime type error: INDEX_SET expects array or map\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
13
src/vm_case_join.inc
Normal file
13
src/vm_case_join.inc
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
case OP_JOIN: {
|
||||||
|
Value sep = pop_value(vm);
|
||||||
|
Value arr = pop_value(vm);
|
||||||
|
if (arr.type != VAL_ARRAY || sep.type != VAL_STRING) {
|
||||||
|
fprintf(stderr, "Runtime type error: JOIN expects (array, string)\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
Value out = bi_join(&arr, &sep);
|
||||||
|
free_value(arr);
|
||||||
|
free_value(sep);
|
||||||
|
push_value(vm, out);
|
||||||
|
break;
|
||||||
|
}
|
||||||
4
src/vm_case_jump.inc
Normal file
4
src/vm_case_jump.inc
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
case OP_JUMP: {
|
||||||
|
f->ip = inst.operand;
|
||||||
|
break;
|
||||||
|
}
|
||||||
9
src/vm_case_jump_if_false.inc
Normal file
9
src/vm_case_jump_if_false.inc
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
case OP_JUMP_IF_FALSE: {
|
||||||
|
Value cond = pop_value(vm);
|
||||||
|
int truthy = value_is_truthy(&cond);
|
||||||
|
free_value(cond);
|
||||||
|
if (!truthy) {
|
||||||
|
f->ip = inst.operand;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
8
src/vm_case_keys.inc
Normal file
8
src/vm_case_keys.inc
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
case OP_KEYS: {
|
||||||
|
Value m = pop_value(vm);
|
||||||
|
if (m.type != VAL_MAP) { fprintf(stderr, "KEYS expects map\n"); exit(1); }
|
||||||
|
Value arr = map_keys_array(&m);
|
||||||
|
free_value(m);
|
||||||
|
push_value(vm, arr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
16
src/vm_case_len.inc
Normal file
16
src/vm_case_len.inc
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
case OP_LEN: {
|
||||||
|
Value a = pop_value(vm);
|
||||||
|
int len = 0;
|
||||||
|
if (a.type == VAL_STRING) {
|
||||||
|
len = (int)(a.s ? (int)strlen(a.s) : 0);
|
||||||
|
} else if (a.type == VAL_ARRAY) {
|
||||||
|
len = array_length(&a);
|
||||||
|
if (len < 0) len = 0;
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Runtime type error: LEN expects array or string\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
free_value(a);
|
||||||
|
push_value(vm, make_int(len));
|
||||||
|
break;
|
||||||
|
}
|
||||||
10
src/vm_case_load_const.inc
Normal file
10
src/vm_case_load_const.inc
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
case OP_LOAD_CONST: {
|
||||||
|
int idx = inst.operand;
|
||||||
|
if (idx < 0 || idx >= f->fn->const_count) {
|
||||||
|
fprintf(stderr, "Runtime error: constant index out of range\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
Value c = copy_value(&f->fn->constants[idx]);
|
||||||
|
push_value(vm, c);
|
||||||
|
break;
|
||||||
|
}
|
||||||
9
src/vm_case_load_global.inc
Normal file
9
src/vm_case_load_global.inc
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
case OP_LOAD_GLOBAL: {
|
||||||
|
int idx = inst.operand;
|
||||||
|
if (idx < 0 || idx >= VM_MAX_GLOBALS) {
|
||||||
|
fprintf(stderr, "Runtime error: global index out of range\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
push_value(vm, copy_value(&vm->globals[idx]));
|
||||||
|
break;
|
||||||
|
}
|
||||||
10
src/vm_case_load_local.inc
Normal file
10
src/vm_case_load_local.inc
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
case OP_LOAD_LOCAL: {
|
||||||
|
int slot = inst.operand;
|
||||||
|
if (slot < 0 || slot >= FRAME_MAX_LOCALS) {
|
||||||
|
fprintf(stderr, "Runtime error: local slot out of range\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
Value val = copy_value(&f->locals[slot]);
|
||||||
|
push_value(vm, val);
|
||||||
|
break;
|
||||||
|
}
|
||||||
14
src/vm_case_lt.inc
Normal file
14
src/vm_case_lt.inc
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
case OP_LT: {
|
||||||
|
Value b = pop_value(vm);
|
||||||
|
Value a = pop_value(vm);
|
||||||
|
if (a.type != VAL_INT || b.type != VAL_INT) {
|
||||||
|
fprintf(stderr, "Runtime type error: LT expects ints, got %s and %s\n",
|
||||||
|
value_type_name(a.type), value_type_name(b.type));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
Value res = make_int(a.i < b.i ? 1 : 0);
|
||||||
|
free_value(a);
|
||||||
|
free_value(b);
|
||||||
|
push_value(vm, res);
|
||||||
|
break;
|
||||||
|
}
|
||||||
13
src/vm_case_lte.inc
Normal file
13
src/vm_case_lte.inc
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
case OP_LTE: {
|
||||||
|
Value b = pop_value(vm);
|
||||||
|
Value a = pop_value(vm);
|
||||||
|
if (a.type != VAL_INT || b.type != VAL_INT) {
|
||||||
|
fprintf(stderr, "Runtime type error: LTE expects ints\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
Value res = make_int(a.i <= b.i ? 1 : 0);
|
||||||
|
free_value(a);
|
||||||
|
free_value(b);
|
||||||
|
push_value(vm, res);
|
||||||
|
break;
|
||||||
|
}
|
||||||
21
src/vm_case_make_array.inc
Normal file
21
src/vm_case_make_array.inc
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
case OP_MAKE_ARRAY: {
|
||||||
|
int n = inst.operand;
|
||||||
|
if (n < 0 || vm->sp + 1 < n) {
|
||||||
|
fprintf(stderr, "Runtime error: invalid element count for MAKE_ARRAY\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
/* pop n values into temp array preserving original order */
|
||||||
|
Value *vals = (Value*)malloc(sizeof(Value) * n);
|
||||||
|
if (!vals) { fprintf(stderr, "Runtime error: OOM in MAKE_ARRAY\n"); exit(1); }
|
||||||
|
for (int i = n - 1; i >= 0; --i) {
|
||||||
|
vals[i] = pop_value(vm); /* take ownership */
|
||||||
|
}
|
||||||
|
/* build array by copying values, then free originals */
|
||||||
|
Value arr = make_array_from_values(vals, n);
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
free_value(vals[i]);
|
||||||
|
}
|
||||||
|
free(vals);
|
||||||
|
push_value(vm, arr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
17
src/vm_case_make_map.inc
Normal file
17
src/vm_case_make_map.inc
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
case OP_MAKE_MAP: {
|
||||||
|
int pairs = inst.operand;
|
||||||
|
if (pairs < 0) { fprintf(stderr, "MAKE_MAP invalid pair count\n"); exit(1); }
|
||||||
|
Value m = make_map_empty();
|
||||||
|
for (int i = 0; i < pairs; ++i) {
|
||||||
|
Value val = pop_value(vm);
|
||||||
|
Value key = pop_value(vm);
|
||||||
|
if (key.type != VAL_STRING) { fprintf(stderr, "Map literal keys must be strings\n"); exit(1); }
|
||||||
|
if (!map_set(&m, key.s ? key.s : "", val)) {
|
||||||
|
fprintf(stderr, "Map literal set failed\n"); exit(1);
|
||||||
|
}
|
||||||
|
free_value(key);
|
||||||
|
/* val ownership moved */
|
||||||
|
}
|
||||||
|
push_value(vm, m);
|
||||||
|
break;
|
||||||
|
}
|
||||||
8
src/vm_case_max.inc
Normal file
8
src/vm_case_max.inc
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
case OP_MAX: {
|
||||||
|
Value b = pop_value(vm);
|
||||||
|
Value a = pop_value(vm);
|
||||||
|
if (a.type != VAL_INT || b.type != VAL_INT) { fprintf(stderr, "MAX expects ints\n"); exit(1); }
|
||||||
|
push_value(vm, make_int(a.i > b.i ? a.i : b.i));
|
||||||
|
free_value(a); free_value(b);
|
||||||
|
break;
|
||||||
|
}
|
||||||
8
src/vm_case_min.inc
Normal file
8
src/vm_case_min.inc
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
case OP_MIN: {
|
||||||
|
Value b = pop_value(vm);
|
||||||
|
Value a = pop_value(vm);
|
||||||
|
if (a.type != VAL_INT || b.type != VAL_INT) { fprintf(stderr, "MIN expects ints\n"); exit(1); }
|
||||||
|
push_value(vm, make_int(a.i < b.i ? a.i : b.i));
|
||||||
|
free_value(a); free_value(b);
|
||||||
|
break;
|
||||||
|
}
|
||||||
18
src/vm_case_mod.inc
Normal file
18
src/vm_case_mod.inc
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
case OP_MOD: {
|
||||||
|
Value b = pop_value(vm);
|
||||||
|
Value a = pop_value(vm);
|
||||||
|
if (a.type != VAL_INT || b.type != VAL_INT) {
|
||||||
|
fprintf(stderr, "Runtime type error: MOD expects ints, got %s and %s\n",
|
||||||
|
value_type_name(a.type), value_type_name(b.type));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (b.i == 0) {
|
||||||
|
fprintf(stderr, "Runtime error: modulo by zero\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
Value res = make_int(a.i % b.i);
|
||||||
|
free_value(a);
|
||||||
|
free_value(b);
|
||||||
|
push_value(vm, res);
|
||||||
|
break;
|
||||||
|
}
|
||||||
14
src/vm_case_mul.inc
Normal file
14
src/vm_case_mul.inc
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
case OP_MUL: {
|
||||||
|
Value b = pop_value(vm);
|
||||||
|
Value a = pop_value(vm);
|
||||||
|
if (a.type != VAL_INT || b.type != VAL_INT) {
|
||||||
|
fprintf(stderr, "Runtime type error: MUL expects ints, got %s and %s\n",
|
||||||
|
value_type_name(a.type), value_type_name(b.type));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
Value res = make_int(a.i * b.i);
|
||||||
|
free_value(a);
|
||||||
|
free_value(b);
|
||||||
|
push_value(vm, res);
|
||||||
|
break;
|
||||||
|
}
|
||||||
19
src/vm_case_neq.inc
Normal file
19
src/vm_case_neq.inc
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
case OP_NEQ: {
|
||||||
|
Value b = pop_value(vm);
|
||||||
|
Value a = pop_value(vm);
|
||||||
|
int neq = 1;
|
||||||
|
if (a.type == b.type) {
|
||||||
|
switch (a.type) {
|
||||||
|
case VAL_INT: neq = (a.i != b.i); break;
|
||||||
|
case VAL_STRING: neq = (a.s && b.s) ? (strcmp(a.s, b.s) != 0) : (a.s != b.s); break;
|
||||||
|
case VAL_FUNCTION: neq = (a.fn != b.fn); break;
|
||||||
|
case VAL_NIL: neq = 0; break;
|
||||||
|
default: neq = 1; break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
neq = 1;
|
||||||
|
}
|
||||||
|
push_value(vm, make_int(neq ? 1 : 0));
|
||||||
|
free_value(a); free_value(b);
|
||||||
|
break;
|
||||||
|
}
|
||||||
2
src/vm_case_nop.inc
Normal file
2
src/vm_case_nop.inc
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
case OP_NOP:
|
||||||
|
break;
|
||||||
7
src/vm_case_not.inc
Normal file
7
src/vm_case_not.inc
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
case OP_NOT: {
|
||||||
|
Value v = pop_value(vm);
|
||||||
|
int res = !value_is_truthy(&v);
|
||||||
|
free_value(v);
|
||||||
|
push_value(vm, make_int(res));
|
||||||
|
break;
|
||||||
|
}
|
||||||
9
src/vm_case_or.inc
Normal file
9
src/vm_case_or.inc
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
case OP_OR: {
|
||||||
|
Value b = pop_value(vm);
|
||||||
|
Value a = pop_value(vm);
|
||||||
|
int res = value_is_truthy(&a) || value_is_truthy(&b);
|
||||||
|
free_value(a);
|
||||||
|
free_value(b);
|
||||||
|
push_value(vm, make_int(res));
|
||||||
|
break;
|
||||||
|
}
|
||||||
9
src/vm_case_pop.inc
Normal file
9
src/vm_case_pop.inc
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
case OP_POP: {
|
||||||
|
if (vm->sp < 0) {
|
||||||
|
fprintf(stderr, "Runtime error: stack underflow for POP\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
Value v = pop_value(vm);
|
||||||
|
free_value(v); // free the popped value
|
||||||
|
break;
|
||||||
|
}
|
||||||
19
src/vm_case_pow.inc
Normal file
19
src/vm_case_pow.inc
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
case OP_POW: {
|
||||||
|
Value b = pop_value(vm);
|
||||||
|
Value a = pop_value(vm);
|
||||||
|
if (a.type != VAL_INT || b.type != VAL_INT) { fprintf(stderr, "POW expects ints\n"); exit(1); }
|
||||||
|
/* simple integer pow */
|
||||||
|
int64_t base = a.i;
|
||||||
|
int64_t exp = b.i;
|
||||||
|
int64_t res = 1;
|
||||||
|
if (exp < 0) { res = 0; } else {
|
||||||
|
while (exp > 0) {
|
||||||
|
if (exp & 1) res *= base;
|
||||||
|
base *= base;
|
||||||
|
exp >>= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
push_value(vm, make_int(res));
|
||||||
|
free_value(a); free_value(b);
|
||||||
|
break;
|
||||||
|
}
|
||||||
14
src/vm_case_print.inc
Normal file
14
src/vm_case_print.inc
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
case OP_PRINT: {
|
||||||
|
Value v = pop_value(vm);
|
||||||
|
/* snapshot value at print time (deep copy arrays) */
|
||||||
|
Value snap = deep_copy_value(&v);
|
||||||
|
free_value(v);
|
||||||
|
if (vm->output_count < VM_OUTPUT_SIZE) {
|
||||||
|
vm->output[vm->output_count++] = snap; // store snapshot
|
||||||
|
} else {
|
||||||
|
free_value(snap); // prevent leak
|
||||||
|
fprintf(stderr, "Runtime error: output buffer overflow\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
12
src/vm_case_random_int.inc
Normal file
12
src/vm_case_random_int.inc
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
case OP_RANDOM_INT: {
|
||||||
|
Value hi = pop_value(vm);
|
||||||
|
Value lo = pop_value(vm);
|
||||||
|
if (lo.type != VAL_INT || hi.type != VAL_INT) { fprintf(stderr, "RANDOM_INT expects (int, int)\n"); exit(1); }
|
||||||
|
int64_t a = lo.i, b = hi.i;
|
||||||
|
if (b <= a) { push_value(vm, make_int((int64_t)a)); free_value(lo); free_value(hi); break; }
|
||||||
|
int64_t span = b - a;
|
||||||
|
int64_t r = (int64_t)(rand() % (span));
|
||||||
|
push_value(vm, make_int(a + r));
|
||||||
|
free_value(lo); free_value(hi);
|
||||||
|
break;
|
||||||
|
}
|
||||||
8
src/vm_case_random_seed.inc
Normal file
8
src/vm_case_random_seed.inc
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
case OP_RANDOM_SEED: {
|
||||||
|
Value seed = pop_value(vm);
|
||||||
|
if (seed.type != VAL_INT) { fprintf(stderr, "RANDOM_SEED expects int\n"); exit(1); }
|
||||||
|
srand((unsigned int)seed.i);
|
||||||
|
free_value(seed);
|
||||||
|
push_value(vm, make_int(0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
21
src/vm_case_read_file.inc
Normal file
21
src/vm_case_read_file.inc
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
case OP_READ_FILE: {
|
||||||
|
Value path = pop_value(vm);
|
||||||
|
if (path.type != VAL_STRING) { fprintf(stderr, "READ_FILE expects string\n"); exit(1); }
|
||||||
|
const char *p = path.s ? path.s : "";
|
||||||
|
FILE *f = fopen(p, "rb");
|
||||||
|
if (!f) { free_value(path); push_value(vm, make_string("")); break; }
|
||||||
|
if (fseek(f, 0, SEEK_END) != 0) { fclose(f); free_value(path); push_value(vm, make_string("")); break; }
|
||||||
|
long sz = ftell(f);
|
||||||
|
if (sz < 0) { fclose(f); free_value(path); push_value(vm, make_string("")); break; }
|
||||||
|
rewind(f);
|
||||||
|
char *buf = (char*)malloc((size_t)sz + 1);
|
||||||
|
size_t n = buf ? fread(buf, 1, (size_t)sz, f) : 0;
|
||||||
|
fclose(f);
|
||||||
|
if (!buf) { free_value(path); push_value(vm, make_string("")); break; }
|
||||||
|
buf[n] = '\0';
|
||||||
|
Value out = make_string(buf);
|
||||||
|
free(buf);
|
||||||
|
free_value(path);
|
||||||
|
push_value(vm, out);
|
||||||
|
break;
|
||||||
|
}
|
||||||
13
src/vm_case_return.inc
Normal file
13
src/vm_case_return.inc
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
case OP_RETURN: {
|
||||||
|
Value retv;
|
||||||
|
/* if there's something on the stack -> return it, else nil */
|
||||||
|
if (vm->sp >= 0) retv = pop_value(vm);
|
||||||
|
else retv = make_nil();
|
||||||
|
|
||||||
|
/* pop frame (free locals) */
|
||||||
|
vm_pop_frame(vm);
|
||||||
|
|
||||||
|
/* push return value into caller frame (or onto stack if no caller) */
|
||||||
|
push_value(vm, retv);
|
||||||
|
break;
|
||||||
|
}
|
||||||
15
src/vm_case_slice.inc
Normal file
15
src/vm_case_slice.inc
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
case OP_SLICE: {
|
||||||
|
Value end = pop_value(vm);
|
||||||
|
Value start = pop_value(vm);
|
||||||
|
Value arr = pop_value(vm);
|
||||||
|
if (arr.type != VAL_ARRAY || start.type != VAL_INT || end.type != VAL_INT) {
|
||||||
|
fprintf(stderr, "Runtime type error: SLICE expects (array, int, int)\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
Value out = array_slice(&arr, (int)start.i, (int)end.i);
|
||||||
|
free_value(arr);
|
||||||
|
free_value(start);
|
||||||
|
free_value(end);
|
||||||
|
push_value(vm, out);
|
||||||
|
break;
|
||||||
|
}
|
||||||
13
src/vm_case_split.inc
Normal file
13
src/vm_case_split.inc
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
case OP_SPLIT: {
|
||||||
|
Value sep = pop_value(vm);
|
||||||
|
Value str = pop_value(vm);
|
||||||
|
if (str.type != VAL_STRING || sep.type != VAL_STRING) {
|
||||||
|
fprintf(stderr, "Runtime type error: SPLIT expects (string, string)\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
Value out = bi_split(&str, &sep);
|
||||||
|
free_value(str);
|
||||||
|
free_value(sep);
|
||||||
|
push_value(vm, out);
|
||||||
|
break;
|
||||||
|
}
|
||||||
11
src/vm_case_store_global.inc
Normal file
11
src/vm_case_store_global.inc
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
case OP_STORE_GLOBAL: {
|
||||||
|
int idx = inst.operand;
|
||||||
|
if (idx < 0 || idx >= VM_MAX_GLOBALS) {
|
||||||
|
fprintf(stderr, "Runtime error: global index out of range\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
Value v = pop_value(vm);
|
||||||
|
free_value(vm->globals[idx]);
|
||||||
|
vm->globals[idx] = v;
|
||||||
|
break;
|
||||||
|
}
|
||||||
12
src/vm_case_store_local.inc
Normal file
12
src/vm_case_store_local.inc
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
case OP_STORE_LOCAL: {
|
||||||
|
int slot = inst.operand;
|
||||||
|
if (slot < 0 || slot >= FRAME_MAX_LOCALS) {
|
||||||
|
fprintf(stderr, "Runtime error: local slot out of range\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
Value v = pop_value(vm);
|
||||||
|
/* free previous local then move v into it */
|
||||||
|
free_value(f->locals[slot]);
|
||||||
|
f->locals[slot] = v;
|
||||||
|
break;
|
||||||
|
}
|
||||||
14
src/vm_case_sub.inc
Normal file
14
src/vm_case_sub.inc
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
case OP_SUB: {
|
||||||
|
Value b = pop_value(vm);
|
||||||
|
Value a = pop_value(vm);
|
||||||
|
if (a.type != VAL_INT || b.type != VAL_INT) {
|
||||||
|
fprintf(stderr, "Runtime type error: SUB expects ints, got %s and %s\n",
|
||||||
|
value_type_name(a.type), value_type_name(b.type));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
Value res = make_int(a.i - b.i);
|
||||||
|
free_value(a);
|
||||||
|
free_value(b);
|
||||||
|
push_value(vm, res);
|
||||||
|
break;
|
||||||
|
}
|
||||||
15
src/vm_case_substr.inc
Normal file
15
src/vm_case_substr.inc
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
case OP_SUBSTR: {
|
||||||
|
Value lenv = pop_value(vm);
|
||||||
|
Value startv = pop_value(vm);
|
||||||
|
Value str = pop_value(vm);
|
||||||
|
if (str.type != VAL_STRING || startv.type != VAL_INT || lenv.type != VAL_INT) {
|
||||||
|
fprintf(stderr, "Runtime type error: SUBSTR expects (string, int, int)\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
Value out = bi_substr(&str, (int)startv.i, (int)lenv.i);
|
||||||
|
free_value(str);
|
||||||
|
free_value(startv);
|
||||||
|
free_value(lenv);
|
||||||
|
push_value(vm, out);
|
||||||
|
break;
|
||||||
|
}
|
||||||
11
src/vm_case_swap.inc
Normal file
11
src/vm_case_swap.inc
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
case OP_SWAP: {
|
||||||
|
if (vm->sp < 1) {
|
||||||
|
fprintf(stderr, "Runtime error: stack underflow for SWAP\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
Value a = vm->stack[vm->sp];
|
||||||
|
Value b = vm->stack[vm->sp - 1];
|
||||||
|
vm->stack[vm->sp] = b;
|
||||||
|
vm->stack[vm->sp - 1] = a;
|
||||||
|
break;
|
||||||
|
}
|
||||||
30
src/vm_case_to_number.inc
Normal file
30
src/vm_case_to_number.inc
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
case OP_TO_NUMBER: {
|
||||||
|
Value v = pop_value(vm);
|
||||||
|
if (v.type == VAL_INT) {
|
||||||
|
/* pass-through */
|
||||||
|
Value out = make_int(v.i);
|
||||||
|
free_value(v);
|
||||||
|
push_value(vm, out);
|
||||||
|
} else if (v.type == VAL_STRING) {
|
||||||
|
const char *s = v.s ? v.s : "";
|
||||||
|
/* trim spaces */
|
||||||
|
const char *p = s;
|
||||||
|
while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p++;
|
||||||
|
char *endp = NULL;
|
||||||
|
long long parsed = strtoll(p, &endp, 10);
|
||||||
|
/* skip trailing spaces */
|
||||||
|
while (endp && (*endp == ' ' || *endp == '\t' || *endp == '\r' || *endp == '\n')) endp++;
|
||||||
|
if (endp && *endp != '\0') {
|
||||||
|
/* non-numeric suffix -> 0 */
|
||||||
|
push_value(vm, make_int(0));
|
||||||
|
} else {
|
||||||
|
push_value(vm, make_int((int64_t)parsed));
|
||||||
|
}
|
||||||
|
free_value(v);
|
||||||
|
} else {
|
||||||
|
/* nil, array, function -> 0 */
|
||||||
|
free_value(v);
|
||||||
|
push_value(vm, make_int(0));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
9
src/vm_case_to_string.inc
Normal file
9
src/vm_case_to_string.inc
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
case OP_TO_STRING: {
|
||||||
|
Value v = pop_value(vm);
|
||||||
|
char *s = value_to_string_alloc(&v);
|
||||||
|
Value out = make_string(s ? s : "");
|
||||||
|
if (s) free(s);
|
||||||
|
free_value(v);
|
||||||
|
push_value(vm, out);
|
||||||
|
break;
|
||||||
|
}
|
||||||
8
src/vm_case_values.inc
Normal file
8
src/vm_case_values.inc
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
case OP_VALUES: {
|
||||||
|
Value m = pop_value(vm);
|
||||||
|
if (m.type != VAL_MAP) { fprintf(stderr, "VALUES expects map\n"); exit(1); }
|
||||||
|
Value arr = map_values_array(&m);
|
||||||
|
free_value(m);
|
||||||
|
push_value(vm, arr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
17
src/vm_case_write_file.inc
Normal file
17
src/vm_case_write_file.inc
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
case OP_WRITE_FILE: {
|
||||||
|
Value data = pop_value(vm);
|
||||||
|
Value path = pop_value(vm);
|
||||||
|
if (path.type != VAL_STRING || data.type != VAL_STRING) { fprintf(stderr, "WRITE_FILE expects (string, string)\n"); exit(1); }
|
||||||
|
const char *p = path.s ? path.s : "";
|
||||||
|
FILE *f = fopen(p, "wb");
|
||||||
|
int ok = 0;
|
||||||
|
if (f) {
|
||||||
|
size_t len = data.s ? strlen(data.s) : 0;
|
||||||
|
ok = (fwrite(data.s ? data.s : "", 1, len, f) == len);
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
free_value(path);
|
||||||
|
free_value(data);
|
||||||
|
push_value(vm, make_int(ok ? 1 : 0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
13
src/vm_case_zip.inc
Normal file
13
src/vm_case_zip.inc
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
case OP_ZIP: {
|
||||||
|
Value b = pop_value(vm);
|
||||||
|
Value a = pop_value(vm);
|
||||||
|
if (a.type != VAL_ARRAY || b.type != VAL_ARRAY) {
|
||||||
|
fprintf(stderr, "Runtime type error: ZIP expects (array, array)\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
Value out = bi_zip(&a, &b);
|
||||||
|
free_value(a);
|
||||||
|
free_value(b);
|
||||||
|
push_value(vm, out);
|
||||||
|
break;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue