diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c370c1..815e78c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.27.2 LANGUAGES C) +project(fun VERSION 0.28.0 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) @@ -44,6 +44,27 @@ if(FUN_WITH_PCSC) endif() endif() +# Optional JSON (json-c) support +option(FUN_WITH_JSON "Enable JSON (json-c) support" OFF) +set(JSONC_INCLUDE_DIRS "") +set(JSONC_LINK_LIBS "") +if(FUN_WITH_JSON) + message(STATUS "Building with JSON (json-c) support") + add_definitions(-DFUN_WITH_JSON) + find_package(PkgConfig QUIET) + if(PKG_CONFIG_FOUND) + pkg_check_modules(JSONC QUIET json-c) + endif() + if(JSONC_FOUND) + list(APPEND JSONC_INCLUDE_DIRS ${JSONC_INCLUDE_DIRS} ${JSONC_INCLUDE_DIRS}) + list(APPEND JSONC_LINK_LIBS ${JSONC_LINK_LIBS} ${JSONC_LIBRARIES}) + include_directories(${JSONC_INCLUDE_DIRS}) + else() + # Fallback: try plain -ljson-c + list(APPEND JSONC_LINK_LIBS json-c) + endif() +endif() + # Debug option to enable verbose parser/VM logging option(FUN_DEBUG "Enable extra debug logging in Fun" OFF) @@ -81,6 +102,14 @@ if(PCSC_LINK_LIBS) target_link_libraries(fun_core PUBLIC ${PCSC_LINK_LIBS}) endif() +# json-c include and link (if enabled) +if(JSONC_INCLUDE_DIRS) + target_include_directories(fun_core PRIVATE ${JSONC_INCLUDE_DIRS}) +endif() +if(JSONC_LINK_LIBS) + target_link_libraries(fun_core PUBLIC ${JSONC_LINK_LIBS}) +endif() + # Link threads if available on UNIX if(Threads_FOUND) target_link_libraries(fun_core PUBLIC Threads::Threads) diff --git a/README.md b/README.md index ad3cf71..1ca220b 100644 --- a/README.md +++ b/README.md @@ -127,10 +127,18 @@ cd fun Build: ```bash -cmake -S . -B build -DFUN_DEBUG=OFF -DFUN_WITH_PCSC=OFF -DFUN_WITH_REPL=ON +# Note: Every -D flag must be of the form NAME=VALUE (e.g., -DFUN_WITH_REPL=ON) +cmake -S . -B build -DFUN_DEBUG=OFF -DFUN_WITH_PCSC=OFF -DFUN_WITH_REPL=ON -DFUN_WITH_JSON=ON cmake --build build --target fun ``` +CMake options you can toggle (all require NAME=VALUE): + +- FUN_DEBUG=ON|OFF — verbose debug logging in the VM (default OFF) +- FUN_WITH_REPL=ON|OFF — enable building the interactive REPL (default ON) +- FUN_WITH_PCSC=ON|OFF — enable PC/SC smart card support (default OFF) +- FUN_WITH_JSON=ON|OFF — enable JSON support via json-c (default ON) + That's it! For testing it, run: ```bash @@ -160,6 +168,15 @@ FUN_LIB_DIR="$(pwd)/lib" ./build/fun But be sure to build Fun with -DFUN_WITH_REPL=ON. +Tip: If you saw an error like this when configuring with CMake: + + CMake Error: Parse error in command line argument: FUN_WITH_JSON + Should be: VAR:type=value + +it means a -D flag was passed without a value. Always specify options as -DNAME=VALUE, for example: + + -DFUN_WITH_JSON=ON + ## Author Johannes Findeisen diff --git a/docs/handbook.md b/docs/handbook.md index bcf974d..103de15 100644 --- a/docs/handbook.md +++ b/docs/handbook.md @@ -46,7 +46,8 @@ cd fun Build: ```bash -cmake -S . -B build -DFUN_DEBUG=OFF -DFUN_WITH_PCSC=OFF -DFUN_WITH_REPL=ON +# Note: Every -D flag must be of the form NAME=VALUE (e.g., -DFUN_WITH_REPL=ON) +cmake -S . -B build -DFUN_DEBUG=OFF -DFUN_WITH_PCSC=OFF -DFUN_WITH_REPL=ON -DFUN_WITH_JSON=ON cmake --build build --target fun ``` @@ -79,6 +80,22 @@ FUN_LIB_DIR="$(pwd)/lib" ./build/fun But be sure to build Fun with -DFUN_WITH_REPL=ON. +#### CMake options + +All CMake options must be passed as -DNAME=VALUE: + +- FUN_DEBUG=ON|OFF — verbose debug logging in the VM (default OFF) +- FUN_WITH_REPL=ON|OFF — enable the interactive REPL (default ON) +- FUN_WITH_PCSC=ON|OFF — enable PC/SC smart card support (default OFF) +- FUN_WITH_JSON=ON|OFF — enable JSON support via json-c (default OFF) + +If you encounter an error such as: + + CMake Error: Parse error in command line argument: FUN_WITH_JSON + Should be: VAR:type=value + +then a -D option was given without a value. Always use -DNAME=VALUE, for example -DFUN_WITH_JSON=ON. + ### Install Fun to OS I do not recommend installing Fun on your system because it is in a very early diff --git a/examples/data/complex.json b/examples/data/complex.json new file mode 100644 index 0000000..36e7920 --- /dev/null +++ b/examples/data/complex.json @@ -0,0 +1,53 @@ +{ + "project": { + "name": "Fun", + "version": "0.27.2", + "website": "https://fun-lang.xyz", + "license": { + "name": "Apache-2.0", + "url": "https://opensource.org/license/apache-2-0" + } + }, + "features": { + "enabled": ["arrays", "maps", "json", "pcsc"], + "experimental": { + "repl": true, + "sockets": true, + "odbc": false, + "notes": null + } + }, + "users": [ + { + "id": 1, + "name": "Ada", + "roles": ["admin", "math"], + "active": true, + "score": 99.5, + "prefs": { + "theme": "dark", + "editor": {"tabWidth": 2, "font": "Fira Code"} + } + }, + { + "id": 2, + "name": "Linus", + "roles": ["user", "kernel"], + "active": false, + "score": 88, + "prefs": { + "theme": "light", + "editor": {"tabWidth": 8, "font": "Monospace"} + } + } + ], + "metrics": { + "counters": [0, 1, 1, 2, 3, 5, 8], + "latency_ms": {"p50": 1.23, "p90": 3.21, "p99": 12.34}, + "builds": 1234567890123456789, + "last_release_ts": 1732406400000 + }, + "matrix": [[1, 2, 3], [4, 5, 6], [7, 8, 9]], + "notes": "UTF-8 ✓ – emojis: 🚀🔥", + "null_field": null +} diff --git a/examples/json_showcase.fun b/examples/json_showcase.fun new file mode 100644 index 0000000..72c46e9 --- /dev/null +++ b/examples/json_showcase.fun @@ -0,0 +1,65 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-11-24 + */ + +// Demonstrates JSON.parse/stringify/from_file/to_file via the stdlib JSON class. + +include + +json = JSON() + +print("-- JSON: parse from string and pretty print --") + +// Build a sample JSON text with most value types +sample = '{"name":"Ada","active":true,"score":99.5,"count":42,"tags":["C","Ada","Math"],"extra":null}' +obj = json.parse(sample) +print("Dump:") +print(obj) +print(obj["name"]) // Ada +print(obj["active"]) // 1 +print(obj["count"]) // 42 +print(len(obj["tags"])) // 3 + +pretty = json.stringify(obj, 1) +print(pretty) + +print("-- JSON: load from file, inspect, and save pretty to /tmp --") + +// Load non existent json file +path = "examples/data/nonexistent.json" +cfg = json.from_file(path) +print("Dump:") +print(cfg) + +// Load a more complex example shipped with the repo +path = "examples/data/complex.json" +cfg = json.from_file(path) +print("Dump:") +print(cfg) + +// Access nested fields +print(cfg["project"]["name"]) // project name +print(cfg["project"]["version"]) // version string +print(len(cfg["users"])) // number of users + +// Derive a small summary map +summary = {} +summary["user_count"] = len(cfg["users"]) +summary["first_user_name"] = cfg["users"][1]["name"] +summary["features_enabled"] = cfg["features"]["enabled"] + +print(json.stringify(summary, 1)) + +// Write the loaded config back as pretty JSON +out_path = "/tmp/fun_complex_out.json" +ok = json.to_file(out_path, cfg, 1) +print(ok) // 1 on success diff --git a/lib/io/json.fun b/lib/io/json.fun new file mode 100644 index 0000000..bb6dc3d --- /dev/null +++ b/lib/io/json.fun @@ -0,0 +1,32 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-11-24 + */ + +// JSON stdlib abstraction wrapping VM json_* builtins defensively. + +class JSON() + fun parse(this, text) + v = json_parse(to_string(text)) + return v + + fun stringify(this, value, pretty) + if pretty == nil + pretty = 0 + return json_stringify(value, pretty) + + fun from_file(this, path) + return json_from_file(to_string(path)) + + fun to_file(this, path, value, pretty) + if pretty == nil + pretty = 0 + return json_to_file(to_string(path), value, pretty) diff --git a/src/bytecode.c b/src/bytecode.c index a9b3924..7350897 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -141,6 +141,10 @@ static const char *opcode_name(OpCode op) { case OP_SHR: return "SHR"; case OP_ROTL: return "ROTL"; case OP_ROTR: return "ROTR"; + case OP_JSON_PARSE: return "JSON_PARSE"; + case OP_JSON_STRINGIFY: return "JSON_STRINGIFY"; + case OP_JSON_FROM_FILE: return "JSON_FROM_FILE"; + case OP_JSON_TO_FILE: return "JSON_TO_FILE"; case OP_PCSC_ESTABLISH: return "PCSC_ESTABLISH"; case OP_PCSC_RELEASE: return "PCSC_RELEASE"; case OP_PCSC_LIST_READERS: return "PCSC_LIST_READERS"; diff --git a/src/bytecode.h b/src/bytecode.h index 172dce1..845241e 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -141,6 +141,12 @@ typedef enum { OP_ROTL, // pops s, a; pushes rotl32(a, s) OP_ROTR, // pops s, a; pushes rotr32(a, s) + // JSON (json-c) + OP_JSON_PARSE, // pops text string; pushes value (or Nil on error) + OP_JSON_STRINGIFY, // pops pretty(bool), value; pushes string + OP_JSON_FROM_FILE, // pops path string; pushes value (or Nil) + OP_JSON_TO_FILE, // pops pretty(bool), value, path; pushes 1/0 + // PCSC (smart card) opcodes OP_PCSC_ESTABLISH, // returns context id (>0) or 0 OP_PCSC_RELEASE, // pops ctx id; returns 1/0 diff --git a/src/jsonc.c b/src/jsonc.c new file mode 100644 index 0000000..5cb24c1 --- /dev/null +++ b/src/jsonc.c @@ -0,0 +1,111 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-11-24 + */ + +/* json-c helpers and VM opcode cases (included from vm.c) */ + +#include "value.h" +#include "vm.h" + +#ifdef FUN_WITH_JSON + #include + #include +#endif + +/* --- Conversion helpers between json-c and Fun Value --- */ +#ifdef FUN_WITH_JSON +static Value json_to_fun(json_object *j) { + if (!j) return make_nil(); + enum json_type t = json_object_get_type(j); + switch (t) { + case json_type_null: return make_nil(); + case json_type_boolean: return make_bool(json_object_get_boolean(j)); + case json_type_double: return make_float(json_object_get_double(j)); + case json_type_int: return make_int((int64_t)json_object_get_int64(j)); + case json_type_string: return make_string(json_object_get_string(j)); + case json_type_array: { + size_t n = json_object_array_length(j); + if (n == 0) { + return make_array_from_values(NULL, 0); + } + Value *vals = (Value*)malloc(sizeof(Value) * n); + if (!vals) return make_array_from_values(NULL, 0); + for (size_t i = 0; i < n; ++i) { + json_object *item = json_object_array_get_idx(j, (int)i); + vals[i] = json_to_fun(item); + } + Value arr = make_array_from_values(vals, (int)n); + for (size_t i = 0; i < n; ++i) free_value(vals[i]); + free(vals); + return arr; + } + case json_type_object: { + Value map = make_map_empty(); + json_object_object_foreach(j, key, val) { + (void)map_set(&map, key, json_to_fun(val)); + } + return map; + } + default: + return make_nil(); + } +} + +static json_object* fun_to_json(const Value *v) { + switch (v->type) { + case VAL_NIL: return json_object_new_null(); + case VAL_BOOL: return json_object_new_boolean(v->i ? 1 : 0); + case VAL_INT: return json_object_new_int64(v->i); + case VAL_FLOAT: return json_object_new_double(v->d); + case VAL_STRING: return json_object_new_string(v->s ? v->s : ""); + case VAL_ARRAY: { + json_object *arr = json_object_new_array(); + int n = array_length(v); + for (int i = 0; i < n; ++i) { + Value item; + if (array_get_copy(v, i, &item)) { + json_object_array_add(arr, fun_to_json(&item)); + free_value(item); + } else { + json_object_array_add(arr, json_object_new_null()); + } + } + return arr; + } + case VAL_MAP: { + json_object *obj = json_object_new_object(); + /* We don't have an iterator API; use keys() helper */ + Value keys = map_keys_array(v); + int kn = array_length(&keys); + for (int i = 0; i < kn; ++i) { + Value k; + if (!array_get_copy(&keys, i, &k)) continue; + if (k.type == VAL_STRING && k.s) { + Value val; + if (map_get_copy(v, k.s, &val)) { + json_object_object_add(obj, k.s, fun_to_json(&val)); + free_value(val); + } else { + json_object_object_add(obj, k.s, json_object_new_null()); + } + } + free_value(k); + } + free_value(keys); + return obj; + } + default: + /* Fallback: stringify unsupported types */ + return json_object_new_string(""); + } +} +#endif /* FUN_WITH_JSON */ + +/* Note: The VM opcode case handlers are included from vm/vm switch via vm/json/ops.c */ diff --git a/src/parser.c b/src/parser.c index 14015c5..97c27ec 100644 --- a/src/parser.c +++ b/src/parser.c @@ -739,6 +739,45 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) free(name); return 1; } + /* JSON builtins */ + if (strcmp(name, "json_parse") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "json_parse expects (text)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after json_parse arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_JSON_PARSE, 0); + free(name); + return 1; + } + if (strcmp(name, "json_stringify") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "json_stringify expects (value, pretty)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "json_stringify expects (value, pretty)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "json_stringify expects (value, pretty)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after json_stringify args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_JSON_STRINGIFY, 0); + free(name); + return 1; + } + if (strcmp(name, "json_from_file") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "json_from_file expects (path)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after json_from_file arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_JSON_FROM_FILE, 0); + free(name); + return 1; + } + if (strcmp(name, "json_to_file") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "json_to_file expects (path, value, pretty)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "json_to_file expects (path, value, pretty)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "json_to_file expects (path, value, pretty)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "json_to_file expects (path, value, pretty)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "json_to_file expects (path, value, pretty)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after json_to_file args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_JSON_TO_FILE, 0); + free(name); + return 1; + } /* PCSC builtins */ if (strcmp(name, "pcsc_establish") == 0) { (*pos)++; /* '(' */ diff --git a/src/vm.c b/src/vm.c index faa2a80..7093c4d 100644 --- a/src/vm.c +++ b/src/vm.c @@ -12,6 +12,7 @@ #include "map.c" #include "string.c" #include "pcsc.c" +#include "jsonc.c" #include "vm.h" #include "value.h" #include @@ -660,6 +661,12 @@ void vm_run(VM *vm, Bytecode *entry) { #include "vm/pcsc/disconnect.c" #include "vm/pcsc/transmit.c" + /* JSON ops (implemented in jsonc.c, included above) */ + #include "vm/json/parse.c" + #include "vm/json/stringify.c" + #include "vm/json/from_file.c" + #include "vm/json/to_file.c" + #include "vm/strings/find.c" #include "vm/strings/regex_match.c" #include "vm/strings/regex_search.c" diff --git a/src/vm.h b/src/vm.h index 9f393d6..8863bcc 100644 --- a/src/vm.h +++ b/src/vm.h @@ -38,6 +38,7 @@ static const char *opcode_names[] = { "TIME_NOW_MS","CLOCK_MONO_MS","DATE_FORMAT", "THREAD_SPAWN","THREAD_JOIN","SLEEP_MS", "BAND","BOR","BXOR","BNOT","SHL","SHR","ROTL","ROTR", + "JSON_PARSE","JSON_STRINGIFY","JSON_FROM_FILE","JSON_TO_FILE", "PCSC_ESTABLISH","PCSC_RELEASE","PCSC_LIST_READERS","PCSC_CONNECT","PCSC_DISCONNECT","PCSC_TRANSMIT", "SOCK_TCP_LISTEN","SOCK_TCP_ACCEPT","SOCK_TCP_CONNECT","SOCK_SEND","SOCK_RECV","SOCK_CLOSE","SOCK_UNIX_LISTEN","SOCK_UNIX_CONNECT", "EXIT" diff --git a/src/vm/json/from_file.c b/src/vm/json/from_file.c new file mode 100644 index 0000000..370bec0 --- /dev/null +++ b/src/vm/json/from_file.c @@ -0,0 +1,30 @@ +/** +* This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-11-24 + */ + +/* JSON_FROM_FILE */ +case OP_JSON_FROM_FILE: { +#ifdef FUN_WITH_JSON + Value vpath = pop_value(vm); + char *path = value_to_string_alloc(&vpath); + free_value(vpath); + if (!path) { push_value(vm, make_nil()); break; } + json_object *root = json_object_from_file(path); + free(path); + if (!root) { push_value(vm, make_nil()); break; } + Value v = json_to_fun(root); + push_value(vm, v); + json_object_put(root); +#else + Value vpath = pop_value(vm); free_value(vpath); + push_value(vm, make_nil()); +#endif + break; +} diff --git a/src/vm/json/parse.c b/src/vm/json/parse.c new file mode 100644 index 0000000..90cc5cc --- /dev/null +++ b/src/vm/json/parse.c @@ -0,0 +1,38 @@ +/** +* This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-11-24 + */ + +/* JSON_PARSE */ +case OP_JSON_PARSE: { +#ifdef FUN_WITH_JSON + Value text = pop_value(vm); + char *s = value_to_string_alloc(&text); + free_value(text); + if (!s) { push_value(vm, make_nil()); break; } + struct json_tokener *tok = json_tokener_new(); + json_object *root = json_tokener_parse_ex(tok, s, (int)strlen(s)); + enum json_tokener_error jerr = json_tokener_get_error(tok); + json_tokener_free(tok); + free(s); + if (jerr != json_tokener_success) { + push_value(vm, make_nil()); + } else { + Value v = json_to_fun(root); + push_value(vm, v); + json_object_put(root); + } +#else + /* Fallback when JSON is disabled: consume arg, push Nil */ + Value drop = pop_value(vm); + free_value(drop); + push_value(vm, make_nil()); +#endif + break; +} diff --git a/src/vm/json/stringify.c b/src/vm/json/stringify.c new file mode 100644 index 0000000..1c5a45b --- /dev/null +++ b/src/vm/json/stringify.c @@ -0,0 +1,32 @@ +/** +* This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-11-24 + */ + +/* JSON_STRINGIFY */ +case OP_JSON_STRINGIFY: { +#ifdef FUN_WITH_JSON + Value vpretty = pop_value(vm); + Value any = pop_value(vm); + int pretty = (vpretty.type == VAL_BOOL || vpretty.type == VAL_INT) ? (vpretty.i != 0) : 0; + json_object *j = fun_to_json(&any); + int flags = pretty ? JSON_C_TO_STRING_PRETTY : JSON_C_TO_STRING_PLAIN; + const char *js = json_object_to_json_string_ext(j, flags); + push_value(vm, make_string(js ? js : "")); + json_object_put(j); + free_value(vpretty); + free_value(any); +#else + /* Fallback: consume two args, push "null" */ + Value vpretty = pop_value(vm); free_value(vpretty); + Value any = pop_value(vm); free_value(any); + push_value(vm, make_string("null")); +#endif + break; +} diff --git a/src/vm/json/to_file.c b/src/vm/json/to_file.c new file mode 100644 index 0000000..d0a9857 --- /dev/null +++ b/src/vm/json/to_file.c @@ -0,0 +1,37 @@ +/** +* This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-11-24 + */ + +/* JSON_TO_FILE */ +case OP_JSON_TO_FILE: { +#ifdef FUN_WITH_JSON + Value vpretty = pop_value(vm); + Value any = pop_value(vm); + Value vpath = pop_value(vm); + char *path = value_to_string_alloc(&vpath); + int pretty = (vpretty.type == VAL_BOOL || vpretty.type == VAL_INT) ? (vpretty.i != 0) : 0; + free_value(vpretty); + free_value(vpath); + if (!path) { free_value(any); push_value(vm, make_int(0)); break; } + json_object *j = fun_to_json(&any); + int flags = pretty ? JSON_C_TO_STRING_PRETTY : JSON_C_TO_STRING_PLAIN; + int rc = json_object_to_file_ext(path, j, flags); + json_object_put(j); + free(path); + free_value(any); + push_value(vm, make_int(rc == 0 ? 1 : 0)); +#else + Value vpretty = pop_value(vm); free_value(vpretty); + Value any = pop_value(vm); free_value(any); + Value vpath = pop_value(vm); free_value(vpath); + push_value(vm, make_int(0)); +#endif + break; +}