diff --git a/CMakeLists.txt b/CMakeLists.txt index 60c0473..2832db2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.37.39 LANGUAGES C) +project(fun VERSION 0.37.40 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/examples/data/complex.ini b/examples/data/complex.ini index 18084af..2d73052 100644 --- a/examples/data/complex.ini +++ b/examples/data/complex.ini @@ -1,32 +1,29 @@ +[auth] +user = "hanez" +token = "abcd1234" [app] -name = "FunApp" -version = "1.2.3" -debug = "1" - +name = "FunApp" +version = "1.2.3" +debug = "1" [database] -host = "localhost" -port = "5432" -user = "fun" -pass = "secret" -pool_size = "8" -timeout = "2.5" - +host = "localhost" +port = "5432" +user = "fun" +pass = "secret" +pool_size = "8" +timeout = "2.5" [network] -ssl = "yes" -retries = "3" -base_url = "https://api.example.com" - +ssl = "yes" +retries = "3" +base_url = "https://api.example.com" [features] -feature_x = "on" -feature_y = "off" - +feature_x = "on" +feature_y = "off" [paths] -data_dir = "./data" -log_file = "./logs/app.log" - - +data_dir = "./data" +log_file = "./logs/app.log" diff --git a/examples/ini_complex.fun b/examples/ini_complex.fun index af547e7..2707983 100755 --- a/examples/ini_complex.fun +++ b/examples/ini_complex.fun @@ -91,7 +91,7 @@ else retries=3 base_url=https://api.example.com [features] - feature_x=0 + feature_x=1 feature_y=0 [paths] data_dir=./data diff --git a/examples/ini_demo.fun b/examples/ini_demo.fun index 40f9cc4..7895584 100755 --- a/examples/ini_demo.fun +++ b/examples/ini_demo.fun @@ -19,7 +19,7 @@ if h == 0 print("Failed to load " + path) else u = ini_get_string(h, "auth", "user", "guest") - r = ini_get_int(h, "network", "retries", 3) + r = ini_get_int(h, "network", "retries", 5) s = ini_get_bool(h, "network", "ssl", 0) print("user=" + u) print("retries=" + to_string(r)) @@ -33,4 +33,11 @@ else user=����U retries=3 ssl=1 + +I wonder about the user= value when the default value is used! + +It should look like: +user=guest +retries=3 +ssl=1 */ diff --git a/examples/ini_diag.fun b/examples/ini_diag.fun new file mode 100644 index 0000000..f360cd9 --- /dev/null +++ b/examples/ini_diag.fun @@ -0,0 +1,50 @@ +#!/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: 2026-01-02 + */ + +// Minimal diagnostic for INI lookups + +path = "./examples/data/complex.ini" +h = ini_load(path) +print("h=" + to_string(h)) +if h == 0 + print("Failed to load: " + path) +else + print("[try app:name]") + v1 = ini_get_string(h, "app", "name", "") + print("app:name => " + v1) + + print("[try app:version]") + v2 = ini_get_string(h, "app", "version", "") + print("app:version => " + v2) + + print("[try database:port]") + v3 = ini_get_int(h, "database", "port", -1) + print("database:port => " + to_string(v3)) + + print("[try network:ssl]") + v4 = ini_get_bool(h, "network", "ssl", -9) + print("network:ssl => " + to_string(v4)) + + ini_free(h) + +/* Expected output: +h=1 +[try app:name] +app:name => FunApp +[try app:version] +app:version => 1.2.3 +[try database:port] +database:port => 5432 +[try network:ssl] +network:ssl => 1 +*/ diff --git a/lib/io/ini.fun b/lib/io/ini.fun index b6145e5..6261dec 100644 --- a/lib/io/ini.fun +++ b/lib/io/ini.fun @@ -29,7 +29,7 @@ class INI() // Load an INI file from path, closing previous one if open. // Returns handle (>0) or 0 on error. fun load(this, path) - if (this.h > 0) + if (this.is_open()) ini_free(this.h) this.h = 0 p = to_string(path) @@ -39,11 +39,13 @@ class INI() // True if a dictionary is open. fun is_open(this) - return this.h > 0 + // Be robust across runtimes: coerce to number and test non-zero + // Avoid using '>' to prevent integer-only GT errors in VM + return to_number(this.h) != 0 // Close and free resources. Safe to call multiple times. fun close(this) - if (this.h > 0) + if (this.is_open()) ini_free(this.h) this.h = 0 return 1 diff --git a/src/vm.c b/src/vm.c index d349967..b23fbb1 100644 --- a/src/vm.c +++ b/src/vm.c @@ -48,6 +48,11 @@ // Optional by extensions commonly used code. #ifdef's are in each single file. #include "external/curl.c" #include "external/ini.c" +#ifdef FUN_WITH_INI +/* Central INI handle registry and helpers */ +#include "vm/ini/handles.c" +#endif +/* Note: INI opcode handlers are included below; changes in vm/ini/*.c require vm.c to rebuild. */ #include "external/json.c" #include "external/libsql.c" #include "external/pcsc.c" diff --git a/src/vm/ini/get_bool.c b/src/vm/ini/get_bool.c index fa886fb..302e287 100644 --- a/src/vm/ini/get_bool.c +++ b/src/vm/ini/get_bool.c @@ -23,8 +23,37 @@ case OP_INI_GET_BOOL: { dictionary *d = ini_get(h); int outb = def; if (d && sec && key) { - char full[1024]; ini_make_full_key(full, sizeof(full), sec, key); - outb = iniparser_getboolean(d, full, def); + char full[1024]; char alt[1024]; + ini_make_full_key(full, sizeof(full), sec, key); + memcpy(alt, full, sizeof(alt)); + for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) { if (alt[i] == ':') { alt[i] = '.'; break; } } + const char *s = iniparser_getstring(d, full, NULL); + if (!s) s = iniparser_getstring(d, alt, NULL); + if (s) { + /* normalize and parse boolean */ + char buf[256]; + size_t n = strlen(s); + if (n >= 2 && ((s[0]=='"' && s[n-1]=='"') || (s[0]=='\'' && s[n-1]=='\''))) { + size_t copy = (n-2) < sizeof(buf)-1 ? (n-2) : sizeof(buf)-1; + memcpy(buf, s+1, copy); buf[copy] = '\0'; + s = buf; + } + /* trim spaces */ + while (*s && (unsigned char)*s <= ' ') s++; + /* lower copy for textual booleans */ + char lb[256]; size_t li=0; for (; s[li] && li < sizeof(lb)-1; ++li) lb[li] = (char)tolower((unsigned char)s[li]); lb[li]='\0'; + if (strcmp(lb, "true")==0 || strcmp(lb, "yes")==0 || strcmp(lb, "on")==0) { + outb = 1; + } else if (strcmp(lb, "false")==0 || strcmp(lb, "no")==0 || strcmp(lb, "off")==0) { + outb = 0; + } else { + /* numeric */ + char *endp=NULL; long v = strtol(lb, &endp, 10); + outb = (endp && endp!=lb) ? (v!=0) : def; + } + } else { + outb = def; + } } free_value(vdef); free_value(vkey); free_value(vsec); free_value(vh); push_value(vm, make_int(outb ? 1 : 0)); diff --git a/src/vm/ini/get_double.c b/src/vm/ini/get_double.c index 9cf676f..7896621 100644 --- a/src/vm/ini/get_double.c +++ b/src/vm/ini/get_double.c @@ -23,8 +23,27 @@ case OP_INI_GET_DOUBLE: { dictionary *d = ini_get(h); double outd = def; if (d && sec && key) { - char full[1024]; ini_make_full_key(full, sizeof(full), sec, key); - outd = iniparser_getdouble(d, full, def); + char full[1024]; char alt[1024]; + ini_make_full_key(full, sizeof(full), sec, key); + memcpy(alt, full, sizeof(alt)); + for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) { if (alt[i] == ':') { alt[i] = '.'; break; } } + const char *s = iniparser_getstring(d, full, NULL); + if (!s) s = iniparser_getstring(d, alt, NULL); + if (s) { + char buf[256]; + size_t n = strlen(s); + if (n >= 2 && ((s[0]=='"' && s[n-1]=='"') || (s[0]=='\'' && s[n-1]=='\''))) { + size_t copy = (n-2) < sizeof(buf)-1 ? (n-2) : sizeof(buf)-1; + memcpy(buf, s+1, copy); buf[copy] = '\0'; + s = buf; + } + while (*s && (unsigned char)*s <= ' ') s++; + char *endp = NULL; + double v = strtod(s, &endp); + if (endp && endp != s) outd = v; else outd = def; + } else { + outd = def; + } } free_value(vdef); free_value(vkey); free_value(vsec); free_value(vh); push_value(vm, make_float(outd)); diff --git a/src/vm/ini/get_int.c b/src/vm/ini/get_int.c index 930bea9..4577ca3 100644 --- a/src/vm/ini/get_int.c +++ b/src/vm/ini/get_int.c @@ -23,8 +23,29 @@ case OP_INI_GET_INT: { dictionary *d = ini_get(h); int outi = def; if (d && sec && key) { - char full[1024]; ini_make_full_key(full, sizeof(full), sec, key); - outi = iniparser_getint(d, full, def); + char full[1024]; char alt[1024]; + ini_make_full_key(full, sizeof(full), sec, key); + memcpy(alt, full, sizeof(alt)); + for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) { if (alt[i] == ':') { alt[i] = '.'; break; } } + const char *s = iniparser_getstring(d, full, NULL); + if (!s) s = iniparser_getstring(d, alt, NULL); + if (s) { + /* strip optional quotes and parse */ + char buf[256]; + size_t n = strlen(s); + if (n >= 2 && ((s[0]=='"' && s[n-1]=='"') || (s[0]=='\'' && s[n-1]=='\''))) { + size_t copy = (n-2) < sizeof(buf)-1 ? (n-2) : sizeof(buf)-1; + memcpy(buf, s+1, copy); buf[copy] = '\0'; + s = buf; + } + /* skip leading spaces */ + while (*s && (unsigned char)*s <= ' ') s++; + char *endp = NULL; + long v = strtol(s, &endp, 10); + if (endp && endp != s) outi = (int)v; else outi = def; + } else { + outi = def; + } } free_value(vdef); free_value(vkey); free_value(vsec); free_value(vh); push_value(vm, make_int(outi)); diff --git a/src/vm/ini/get_string.c b/src/vm/ini/get_string.c index fcfa36f..ef44106 100644 --- a/src/vm/ini/get_string.c +++ b/src/vm/ini/get_string.c @@ -23,8 +23,19 @@ case OP_INI_GET_STRING: { dictionary *d = ini_get(h); const char *res = def; if (d && sec && key) { - char full[1024]; ini_make_full_key(full, sizeof(full), sec, key); + char full[1024]; char alt[1024]; + ini_make_full_key(full, sizeof(full), sec, key); + /* Build alternate with dot separator for robustness */ + ini_make_full_key(alt, sizeof(alt), sec, key); + size_t flen = strlen(full); + if (flen < sizeof(alt) && flen > 0) { /* create dot version in alt */ + memcpy(alt, full, flen + 1); + for (size_t i = 0; i < flen; ++i) if (alt[i] == ':') { alt[i] = '.'; break; } + } const char *s = iniparser_getstring(d, full, def); + if (s == def) { /* not found, try alternate dot form */ + s = iniparser_getstring(d, alt, def); + } res = s ? s : ""; } free_value(vdef); free_value(vkey); free_value(vsec); free_value(vh); diff --git a/src/vm/ini/handles.c b/src/vm/ini/handles.c new file mode 100644 index 0000000..2515410 --- /dev/null +++ b/src/vm/ini/handles.c @@ -0,0 +1,63 @@ +/* + * 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 + */ + +#ifdef FUN_WITH_INI +#if defined(__has_include) +# if __has_include() +# include +# include +# elif __has_include() +# include +# include +# else +# error "iniparser headers not found" +# endif +#else +# include +# include +#endif + +#include +#include +#include + +#include "handles.h" + +IniSlot g_ini[64]; + +int ini_alloc_handle(dictionary *d) { + if (!d) return 0; + for (int i = 1; i < (int)(sizeof(g_ini)/sizeof(g_ini[0])); ++i) { + if (!g_ini[i].in_use) { g_ini[i].in_use = 1; g_ini[i].dict = d; return i; } + } + return 0; +} + +dictionary* ini_get(int h) { + if (h > 0 && h < (int)(sizeof(g_ini)/sizeof(g_ini[0])) && g_ini[h].in_use) return g_ini[h].dict; + return NULL; +} + +int ini_free_handle(int h) { + if (h <= 0 || h >= (int)(sizeof(g_ini)/sizeof(g_ini[0])) || !g_ini[h].in_use) return 0; + if (g_ini[h].dict) iniparser_freedict(g_ini[h].dict); + g_ini[h].dict = NULL; + g_ini[h].in_use = 0; + return 1; +} + +void ini_make_full_key(char *buf, size_t cap, const char *sec, const char *key) { + if (!buf || cap == 0) return; + if (!sec) sec = ""; + if (!key) key = ""; + /* iniparser expects section:key; lookup is case-insensitive internally */ + snprintf(buf, cap, "%s:%s", sec, key); +} + +#endif /* FUN_WITH_INI */ diff --git a/src/vm/ini/handles.h b/src/vm/ini/handles.h index 3c2022f..831936c 100644 --- a/src/vm/ini/handles.h +++ b/src/vm/ini/handles.h @@ -27,35 +27,18 @@ # include # include #endif -#include /* snprintf for helper */ +#include typedef struct { dictionary *dict; int in_use; } IniSlot; -static IniSlot g_ini[64]; -static int ini_alloc_handle(dictionary *d) { - for (int i = 1; i < (int)(sizeof(g_ini)/sizeof(g_ini[0])); ++i) { - if (!g_ini[i].in_use) { g_ini[i].in_use = 1; g_ini[i].dict = d; return i; } - } - return 0; -} -static dictionary* ini_get(int h) { - if (h > 0 && h < (int)(sizeof(g_ini)/sizeof(g_ini[0])) && g_ini[h].in_use) return g_ini[h].dict; - return NULL; -} -static int ini_free_handle(int h) { - if (h <= 0 || h >= (int)(sizeof(g_ini)/sizeof(g_ini[0])) || !g_ini[h].in_use) return 0; - if (g_ini[h].dict) iniparser_freedict(g_ini[h].dict); - g_ini[h].dict = NULL; - g_ini[h].in_use = 0; - return 1; -} +/* Single global registry (defined in handles.c) */ +extern IniSlot g_ini[64]; -/* Helper to build section:key string safely into provided buffer */ -static inline void ini_make_full_key(char *buf, size_t cap, const char *sec, const char *key) { - if (!buf || cap == 0) return; - if (!sec) sec = ""; - if (!key) key = ""; - /* iniparser expects "section:key" */ - snprintf(buf, cap, "%s:%s", sec, key); -} +/* Registry API (implemented in handles.c) */ +int ini_alloc_handle(dictionary *d); +dictionary* ini_get(int h); +int ini_free_handle(int h); + +/* Helper to build section:key string safely into provided buffer (implemented in handles.c) */ +void ini_make_full_key(char *buf, size_t cap, const char *sec, const char *key); #endif /* FUN_WITH_INI */ diff --git a/src/vm/ini/set.c b/src/vm/ini/set.c index a0affd9..eee9660 100644 --- a/src/vm/ini/set.c +++ b/src/vm/ini/set.c @@ -23,9 +23,16 @@ case OP_INI_SET: { if (d && sec && key) { char *valstr = value_to_string_alloc(&vval); if (valstr) { - char full[1024]; snprintf(full, sizeof(full), "%s:%s", sec, key); + char full[1024]; char alt[1024]; + ini_make_full_key(full, sizeof(full), sec, key); + memcpy(alt, full, sizeof(alt)); + for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) { if (alt[i] == ':') { alt[i] = '.'; break; } } /* iniparser 4.x does not expose iniparser_set; use dictionary_set */ - if (dictionary_set(d, full, valstr) == 0) ok = 1; /* 0 means success */ + if (dictionary_set(d, full, valstr) == 0) { + ok = 1; /* 0 means success */ + } else if (dictionary_set(d, alt, valstr) == 0) { + ok = 1; + } free(valstr); } } diff --git a/src/vm/ini/unset.c b/src/vm/ini/unset.c index d264f72..8c339c7 100644 --- a/src/vm/ini/unset.c +++ b/src/vm/ini/unset.c @@ -20,9 +20,13 @@ case OP_INI_UNSET: { const char *sec = (vsec.type==VAL_STRING)?vsec.s:NULL; int ok = 0; if (d && sec && key) { - char full[1024]; snprintf(full, sizeof(full), "%s:%s", sec, key); - /* iniparser 4.2.6 dictionary_unset returns void; assume success if inputs are valid */ + char full[1024]; char alt[1024]; + ini_make_full_key(full, sizeof(full), sec, key); + memcpy(alt, full, sizeof(alt)); + for (size_t i = 0; i < sizeof(alt) && alt[i]; ++i) { if (alt[i] == ':') { alt[i] = '.'; break; } } + /* iniparser 4.2.6 dictionary_unset returns void; remove both forms */ dictionary_unset(d, full); + dictionary_unset(d, alt); ok = 1; } free_value(vkey); free_value(vsec); free_value(vh);