Phase 5-7: generic inference, extend Type<T>, String stdlib, Map<K,V>, self-hosting audit, docs update
This commit is contained in:
@@ -6,5 +6,22 @@ extern func PrintInt(n: int);
|
||||
extern func PrintFloat(f: float64);
|
||||
extern func PrintBool(b: bool);
|
||||
extern func ReadLine() -> String;
|
||||
extern func bux_read_file(path: String) -> String;
|
||||
extern func bux_write_file(path: String, content: String) -> int;
|
||||
extern func bux_file_exists(path: String) -> int;
|
||||
|
||||
func ReadFile(path: String) -> String {
|
||||
return bux_read_file(path);
|
||||
}
|
||||
|
||||
func WriteFile(path: String, content: String) -> bool {
|
||||
let r: int = bux_write_file(path, content);
|
||||
return r != 0;
|
||||
}
|
||||
|
||||
func FileExists(path: String) -> bool {
|
||||
let r: int = bux_file_exists(path);
|
||||
return r != 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+107
-17
@@ -1,31 +1,120 @@
|
||||
module Std::Map {
|
||||
|
||||
extern func bux_hash_bytes(ptr: *void, size: uint) -> uint;
|
||||
extern func bux_hash_string(s: String) -> uint;
|
||||
|
||||
struct MapEntry {
|
||||
key: String,
|
||||
value: int,
|
||||
// ---------------------------------------------------------------------------
|
||||
// Generic Map<K, V> — works with value-type keys (int, float, etc.)
|
||||
// For String keys, use StringMap below.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct MapEntry<K, V> {
|
||||
key: K,
|
||||
value: V,
|
||||
occupied: bool,
|
||||
}
|
||||
|
||||
struct Map {
|
||||
entries: *MapEntry,
|
||||
struct Map<K, V> {
|
||||
entries: *MapEntry<K, V>,
|
||||
cap: uint,
|
||||
len: uint,
|
||||
}
|
||||
|
||||
func Map_New(cap: uint) -> Map {
|
||||
let total: uint = cap * sizeof(MapEntry);
|
||||
let data: *MapEntry = bux_alloc(total) as *MapEntry;
|
||||
func Map_New<K, V>(cap: uint) -> Map<K, V> {
|
||||
let total: uint = cap * sizeof(MapEntry<K, V>);
|
||||
let data: *MapEntry<K, V> = bux_alloc(total) as *MapEntry<K, V>;
|
||||
var i: uint = 0;
|
||||
while i < cap {
|
||||
data[i].occupied = false;
|
||||
i = i + 1;
|
||||
}
|
||||
return Map { entries: data, cap: cap, len: 0 };
|
||||
return Map<K, V> { entries: data, cap: cap, len: 0 };
|
||||
}
|
||||
|
||||
func Map_Set(m: *Map, key: String, value: int) {
|
||||
func Map_Set<K, V>(m: *Map<K, V>, key: K, value: V) {
|
||||
var keyPtr: *K = &key;
|
||||
let hash: uint = bux_hash_bytes(keyPtr as *void, sizeof(K));
|
||||
var idx: uint = hash % m.cap;
|
||||
while m.entries[idx].occupied {
|
||||
if m.entries[idx].key == key {
|
||||
m.entries[idx].value = value;
|
||||
return;
|
||||
}
|
||||
idx = (idx + 1) % m.cap;
|
||||
}
|
||||
m.entries[idx].key = key;
|
||||
m.entries[idx].value = value;
|
||||
m.entries[idx].occupied = true;
|
||||
m.len = m.len + 1;
|
||||
}
|
||||
|
||||
func Map_Get<K, V>(m: *Map<K, V>, key: K) -> V {
|
||||
var keyPtr: *K = &key;
|
||||
let hash: uint = bux_hash_bytes(keyPtr as *void, sizeof(K));
|
||||
var idx: uint = hash % m.cap;
|
||||
while m.entries[idx].occupied {
|
||||
if m.entries[idx].key == key {
|
||||
return m.entries[idx].value;
|
||||
}
|
||||
idx = (idx + 1) % m.cap;
|
||||
}
|
||||
// Return zero value for missing key
|
||||
var zero: V = 0 as V;
|
||||
return zero;
|
||||
}
|
||||
|
||||
func Map_Has<K, V>(m: *Map<K, V>, key: K) -> bool {
|
||||
var keyPtr: *K = &key;
|
||||
let hash: uint = bux_hash_bytes(keyPtr as *void, sizeof(K));
|
||||
var idx: uint = hash % m.cap;
|
||||
while m.entries[idx].occupied {
|
||||
if m.entries[idx].key == key {
|
||||
return true;
|
||||
}
|
||||
idx = (idx + 1) % m.cap;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
func Map_Len<K, V>(m: *Map<K, V>) -> uint {
|
||||
return m.len;
|
||||
}
|
||||
|
||||
func Map_Free<K, V>(m: *Map<K, V>) {
|
||||
bux_free(m.entries as *void);
|
||||
m.entries = null as *MapEntry<K, V>;
|
||||
m.cap = 0;
|
||||
m.len = 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// StringMap<V> — specialized Map for String keys, using strcmp
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct StringMapEntry<V> {
|
||||
key: String,
|
||||
value: V,
|
||||
occupied: bool,
|
||||
}
|
||||
|
||||
struct StringMap<V> {
|
||||
entries: *StringMapEntry<V>,
|
||||
cap: uint,
|
||||
len: uint,
|
||||
}
|
||||
|
||||
func StringMap_New<V>(cap: uint) -> StringMap<V> {
|
||||
let total: uint = cap * sizeof(StringMapEntry<V>);
|
||||
let data: *StringMapEntry<V> = bux_alloc(total) as *StringMapEntry<V>;
|
||||
var i: uint = 0;
|
||||
while i < cap {
|
||||
data[i].occupied = false;
|
||||
i = i + 1;
|
||||
}
|
||||
return StringMap<V> { entries: data, cap: cap, len: 0 };
|
||||
}
|
||||
|
||||
func StringMap_Set<V>(m: *StringMap<V>, key: String, value: V) {
|
||||
let hash: uint = bux_hash_string(key);
|
||||
var idx: uint = hash % m.cap;
|
||||
while m.entries[idx].occupied {
|
||||
@@ -41,7 +130,7 @@ func Map_Set(m: *Map, key: String, value: int) {
|
||||
m.len = m.len + 1;
|
||||
}
|
||||
|
||||
func Map_Get(m: *Map, key: String) -> int {
|
||||
func StringMap_Get<V>(m: *StringMap<V>, key: String) -> V {
|
||||
let hash: uint = bux_hash_string(key);
|
||||
var idx: uint = hash % m.cap;
|
||||
while m.entries[idx].occupied {
|
||||
@@ -50,10 +139,11 @@ func Map_Get(m: *Map, key: String) -> int {
|
||||
}
|
||||
idx = (idx + 1) % m.cap;
|
||||
}
|
||||
return 0;
|
||||
var zero: V = 0 as V;
|
||||
return zero;
|
||||
}
|
||||
|
||||
func Map_Has(m: *Map, key: String) -> bool {
|
||||
func StringMap_Has<V>(m: *StringMap<V>, key: String) -> bool {
|
||||
let hash: uint = bux_hash_string(key);
|
||||
var idx: uint = hash % m.cap;
|
||||
while m.entries[idx].occupied {
|
||||
@@ -65,15 +155,15 @@ func Map_Has(m: *Map, key: String) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
func Map_Len(m: *Map) -> uint {
|
||||
func StringMap_Len<V>(m: *StringMap<V>) -> uint {
|
||||
return m.len;
|
||||
}
|
||||
|
||||
func Map_Free(m: *Map) {
|
||||
func StringMap_Free<V>(m: *StringMap<V>) {
|
||||
bux_free(m.entries as *void);
|
||||
m.entries = null as *MapEntry;
|
||||
m.entries = null as *StringMapEntry<V>;
|
||||
m.cap = 0;
|
||||
m.len = 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
module Std::Path {
|
||||
|
||||
extern func bux_path_join(a: String, b: String) -> String;
|
||||
extern func bux_path_parent(path: String) -> String;
|
||||
extern func bux_path_ext(path: String) -> String;
|
||||
|
||||
func Path_Join(a: String, b: String) -> String {
|
||||
return bux_path_join(a, b);
|
||||
}
|
||||
|
||||
func Path_Parent(path: String) -> String {
|
||||
return bux_path_parent(path);
|
||||
}
|
||||
|
||||
func Path_Ext(path: String) -> String {
|
||||
return bux_path_ext(path);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,6 +6,25 @@ extern func bux_strncmp(a: String, b: String, n: uint) -> int;
|
||||
extern func bux_strcpy(dest: *char8, src: String) -> *char8;
|
||||
extern func bux_strcat(dest: *char8, src: String) -> *char8;
|
||||
extern func bux_strncpy(dest: *char8, src: String, n: uint) -> *char8;
|
||||
extern func bux_strstr(haystack: String, needle: String) -> String;
|
||||
extern func bux_str_contains(haystack: String, needle: String) -> int;
|
||||
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
|
||||
extern func bux_str_trim_left(s: String) -> String;
|
||||
extern func bux_str_trim_right(s: String) -> String;
|
||||
extern func bux_str_trim(s: String) -> String;
|
||||
extern func bux_int_to_str(n: int64) -> String;
|
||||
extern func bux_str_to_int(s: String) -> int64;
|
||||
extern func bux_sb_new(initial_cap: uint) -> *void;
|
||||
extern func bux_sb_append(sb: *void, s: String);
|
||||
extern func bux_sb_append_int(sb: *void, n: int64);
|
||||
extern func bux_sb_append_float(sb: *void, f: float64);
|
||||
extern func bux_sb_append_char(sb: *void, c: char8);
|
||||
extern func bux_sb_build(sb: *void) -> String;
|
||||
extern func bux_sb_free(sb: *void);
|
||||
extern func bux_str_split_count(s: String, delim: String) -> uint;
|
||||
extern func bux_str_split_part(s: String, delim: String, index: uint) -> String;
|
||||
extern func bux_str_join2(a: String, b: String, sep: String) -> String;
|
||||
|
||||
|
||||
func String_Len(s: String) -> uint {
|
||||
return bux_strlen(s);
|
||||
@@ -42,4 +61,98 @@ func String_StartsWith(s: String, prefix: String) -> bool {
|
||||
return r == 0;
|
||||
}
|
||||
|
||||
func String_EndsWith(s: String, suffix: String) -> bool {
|
||||
let s_len: uint = bux_strlen(s);
|
||||
let suf_len: uint = bux_strlen(suffix);
|
||||
if suf_len > s_len {
|
||||
return false;
|
||||
}
|
||||
let start: uint = s_len - suf_len;
|
||||
let tail: String = bux_str_slice(s, start, suf_len);
|
||||
let eq: bool = bux_strcmp(tail, suffix) == 0;
|
||||
return eq;
|
||||
}
|
||||
|
||||
func String_Contains(s: String, substr: String) -> bool {
|
||||
let r: int = bux_str_contains(s, substr);
|
||||
return r != 0;
|
||||
}
|
||||
|
||||
func String_Slice(s: String, start: uint, len: uint) -> String {
|
||||
return bux_str_slice(s, start, len);
|
||||
}
|
||||
|
||||
func String_Trim(s: String) -> String {
|
||||
return bux_str_trim(s);
|
||||
}
|
||||
|
||||
func String_TrimLeft(s: String) -> String {
|
||||
return bux_str_trim_left(s);
|
||||
}
|
||||
|
||||
func String_TrimRight(s: String) -> String {
|
||||
return bux_str_trim_right(s);
|
||||
}
|
||||
|
||||
func String_FromInt(n: int64) -> String {
|
||||
return bux_int_to_str(n);
|
||||
}
|
||||
|
||||
func String_ToInt(s: String) -> int64 {
|
||||
return bux_str_to_int(s);
|
||||
}
|
||||
|
||||
// String Builder — efficient string construction
|
||||
struct StringBuilder {
|
||||
handle: *void,
|
||||
}
|
||||
|
||||
func StringBuilder_New() -> StringBuilder {
|
||||
return StringBuilder { handle: bux_sb_new(64) };
|
||||
}
|
||||
|
||||
func StringBuilder_NewCap(cap: uint) -> StringBuilder {
|
||||
return StringBuilder { handle: bux_sb_new(cap) };
|
||||
}
|
||||
|
||||
func StringBuilder_Append(sb: *StringBuilder, s: String) {
|
||||
bux_sb_append(sb.handle, s);
|
||||
}
|
||||
|
||||
func StringBuilder_AppendInt(sb: *StringBuilder, n: int64) {
|
||||
bux_sb_append_int(sb.handle, n);
|
||||
}
|
||||
|
||||
func StringBuilder_AppendFloat(sb: *StringBuilder, f: float64) {
|
||||
bux_sb_append_float(sb.handle, f);
|
||||
}
|
||||
|
||||
func StringBuilder_AppendChar(sb: *StringBuilder, c: char8) {
|
||||
bux_sb_append_char(sb.handle, c);
|
||||
}
|
||||
|
||||
func StringBuilder_Build(sb: *StringBuilder) -> String {
|
||||
return bux_sb_build(sb.handle);
|
||||
}
|
||||
|
||||
func StringBuilder_Free(sb: *StringBuilder) {
|
||||
bux_sb_free(sb.handle);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// String split/join
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func String_SplitCount(s: String, delim: String) -> uint {
|
||||
return bux_str_split_count(s, delim);
|
||||
}
|
||||
|
||||
func String_SplitPart(s: String, delim: String, index: uint) -> String {
|
||||
return bux_str_split_part(s, delim, index);
|
||||
}
|
||||
|
||||
func String_Join2(a: String, b: String, sep: String) -> String {
|
||||
return bux_str_join2(a, b, sep);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -153,6 +153,391 @@ char* bux_strncpy(char* dest, const char* src, unsigned int n) {
|
||||
return strncpy(dest, src, (size_t)n);
|
||||
}
|
||||
|
||||
/* String find: returns pointer to first occurrence of needle in haystack, or NULL */
|
||||
const char* bux_strstr(const char* haystack, const char* needle) {
|
||||
if (!haystack || !needle) return NULL;
|
||||
return strstr(haystack, needle);
|
||||
}
|
||||
|
||||
/* String contains: returns 1 if haystack contains needle, 0 otherwise */
|
||||
int bux_str_contains(const char* haystack, const char* needle) {
|
||||
return bux_strstr(haystack, needle) != NULL;
|
||||
}
|
||||
|
||||
/* String slice: extract substring from start, length len */
|
||||
char* bux_str_slice(const char* s, unsigned int start, unsigned int len) {
|
||||
if (!s) return NULL;
|
||||
unsigned int s_len = (unsigned int)strlen(s);
|
||||
if (start >= s_len) {
|
||||
char* empty = (char*)bux_alloc(1);
|
||||
empty[0] = '\0';
|
||||
return empty;
|
||||
}
|
||||
unsigned int avail = s_len - start;
|
||||
if (len > avail) len = avail;
|
||||
char* result = (char*)bux_alloc(len + 1);
|
||||
memcpy(result, s + start, len);
|
||||
result[len] = '\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
/* String trim left: remove leading whitespace */
|
||||
char* bux_str_trim_left(const char* s) {
|
||||
if (!s) return NULL;
|
||||
while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++;
|
||||
unsigned int len = (unsigned int)strlen(s);
|
||||
char* result = (char*)bux_alloc(len + 1);
|
||||
memcpy(result, s, len + 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* String trim right: remove trailing whitespace */
|
||||
char* bux_str_trim_right(const char* s) {
|
||||
if (!s) return NULL;
|
||||
unsigned int len = (unsigned int)strlen(s);
|
||||
while (len > 0 && (s[len-1] == ' ' || s[len-1] == '\t' || s[len-1] == '\n' || s[len-1] == '\r')) {
|
||||
len--;
|
||||
}
|
||||
char* result = (char*)bux_alloc(len + 1);
|
||||
memcpy(result, s, len);
|
||||
result[len] = '\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
/* String trim: remove both leading and trailing whitespace */
|
||||
char* bux_str_trim(const char* s) {
|
||||
char* left = bux_str_trim_left(s);
|
||||
char* result = bux_str_trim_right(left);
|
||||
bux_free(left);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Int to string conversion */
|
||||
char* bux_int_to_str(int64_t n) {
|
||||
char* result = (char*)bux_alloc(32);
|
||||
snprintf(result, 32, "%lld", (long long)n);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* String to int conversion */
|
||||
int64_t bux_str_to_int(const char* s) {
|
||||
if (!s) return 0;
|
||||
return (int64_t)atoll(s);
|
||||
}
|
||||
|
||||
/* String builder */
|
||||
typedef struct {
|
||||
char* buf;
|
||||
unsigned int len;
|
||||
unsigned int cap;
|
||||
} BuxStringBuilder;
|
||||
|
||||
BuxStringBuilder* bux_sb_new(unsigned int initial_cap) {
|
||||
BuxStringBuilder* sb = (BuxStringBuilder*)bux_alloc(sizeof(BuxStringBuilder));
|
||||
sb->cap = initial_cap > 0 ? initial_cap : 64;
|
||||
sb->buf = (char*)bux_alloc(sb->cap);
|
||||
sb->buf[0] = '\0';
|
||||
sb->len = 0;
|
||||
return sb;
|
||||
}
|
||||
|
||||
void bux_sb_append(BuxStringBuilder* sb, const char* s) {
|
||||
if (!sb || !s) return;
|
||||
unsigned int s_len = (unsigned int)strlen(s);
|
||||
unsigned int new_len = sb->len + s_len;
|
||||
if (new_len + 1 > sb->cap) {
|
||||
while (sb->cap < new_len + 1) sb->cap *= 2;
|
||||
sb->buf = (char*)bux_realloc(sb->buf, sb->cap);
|
||||
}
|
||||
memcpy(sb->buf + sb->len, s, s_len);
|
||||
sb->len = new_len;
|
||||
sb->buf[sb->len] = '\0';
|
||||
}
|
||||
|
||||
void bux_sb_append_int(BuxStringBuilder* sb, int64_t n) {
|
||||
char tmp[32];
|
||||
snprintf(tmp, sizeof(tmp), "%lld", (long long)n);
|
||||
bux_sb_append(sb, tmp);
|
||||
}
|
||||
|
||||
void bux_sb_append_float(BuxStringBuilder* sb, double f) {
|
||||
char tmp[32];
|
||||
snprintf(tmp, sizeof(tmp), "%g", f);
|
||||
bux_sb_append(sb, tmp);
|
||||
}
|
||||
|
||||
void bux_sb_append_char(BuxStringBuilder* sb, char c) {
|
||||
if (!sb) return;
|
||||
if (sb->len + 2 > sb->cap) {
|
||||
sb->cap *= 2;
|
||||
sb->buf = (char*)bux_realloc(sb->buf, sb->cap);
|
||||
}
|
||||
sb->buf[sb->len++] = c;
|
||||
sb->buf[sb->len] = '\0';
|
||||
}
|
||||
|
||||
const char* bux_sb_build(BuxStringBuilder* sb) {
|
||||
if (!sb) return "";
|
||||
return sb->buf;
|
||||
}
|
||||
|
||||
void bux_sb_free(BuxStringBuilder* sb) {
|
||||
if (!sb) return;
|
||||
bux_free(sb->buf);
|
||||
bux_free(sb);
|
||||
}
|
||||
|
||||
/* String split: count parts separated by delimiter */
|
||||
unsigned int bux_str_split_count(const char* s, const char* delim) {
|
||||
if (!s || !delim || !*delim) return 1;
|
||||
unsigned int count = 1;
|
||||
size_t delim_len = strlen(delim);
|
||||
const char* p = s;
|
||||
while ((p = strstr(p, delim)) != NULL) {
|
||||
count++;
|
||||
p += delim_len;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* String split: get the n-th part (0-indexed) */
|
||||
char* bux_str_split_part(const char* s, const char* delim, unsigned int index) {
|
||||
if (!s || !delim || !*delim) {
|
||||
if (index == 0) {
|
||||
unsigned int len = s ? (unsigned int)strlen(s) : 0;
|
||||
char* result = (char*)bux_alloc(len + 1);
|
||||
if (s) memcpy(result, s, len);
|
||||
result[len] = '\0';
|
||||
return result;
|
||||
}
|
||||
char* empty = (char*)bux_alloc(1);
|
||||
empty[0] = '\0';
|
||||
return empty;
|
||||
}
|
||||
size_t delim_len = strlen(delim);
|
||||
const char* start = s;
|
||||
const char* end;
|
||||
unsigned int current = 0;
|
||||
while (current < index) {
|
||||
end = strstr(start, delim);
|
||||
if (!end) {
|
||||
char* empty = (char*)bux_alloc(1);
|
||||
empty[0] = '\0';
|
||||
return empty;
|
||||
}
|
||||
start = end + delim_len;
|
||||
current++;
|
||||
}
|
||||
end = strstr(start, delim);
|
||||
size_t part_len;
|
||||
if (end) {
|
||||
part_len = (size_t)(end - start);
|
||||
} else {
|
||||
part_len = strlen(start);
|
||||
}
|
||||
char* result = (char*)bux_alloc(part_len + 1);
|
||||
memcpy(result, start, part_len);
|
||||
result[part_len] = '\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
/* String join: join two strings with separator */
|
||||
char* bux_str_join2(const char* a, const char* b, const char* sep) {
|
||||
if (!a && !b) {
|
||||
char* empty = (char*)bux_alloc(1);
|
||||
empty[0] = '\0';
|
||||
return empty;
|
||||
}
|
||||
unsigned int len_a = a ? (unsigned int)strlen(a) : 0;
|
||||
unsigned int len_b = b ? (unsigned int)strlen(b) : 0;
|
||||
unsigned int len_sep = sep ? (unsigned int)strlen(sep) : 0;
|
||||
unsigned int total = len_a + len_sep + len_b;
|
||||
char* result = (char*)bux_alloc(total + 1);
|
||||
if (a) memcpy(result, a, len_a);
|
||||
if (sep && len_a > 0 && len_b > 0) memcpy(result + len_a, sep, len_sep);
|
||||
if (b) memcpy(result + len_a + (len_a > 0 && len_b > 0 ? len_sep : 0), b, len_b);
|
||||
result[total] = '\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Simple string format: replace {0}, {1}, ... with string arguments.
|
||||
Returns formatted string. Supports up to 8 arguments. */
|
||||
char* bux_str_format(const char* pattern,
|
||||
const char* a0, const char* a1, const char* a2, const char* a3,
|
||||
const char* a4, const char* a5, const char* a6, const char* a7) {
|
||||
if (!pattern) {
|
||||
char* empty = (char*)bux_alloc(1);
|
||||
empty[0] = '\0';
|
||||
return empty;
|
||||
}
|
||||
const char* args[8] = { a0, a1, a2, a3, a4, a5, a6, a7 };
|
||||
|
||||
/* Calculate result size */
|
||||
size_t total = 0;
|
||||
const char* p = pattern;
|
||||
while (*p) {
|
||||
if (*p == '{' && p[1] >= '0' && p[1] <= '7' && p[2] == '}') {
|
||||
int idx = p[1] - '0';
|
||||
if (args[idx]) total += strlen(args[idx]);
|
||||
p += 3;
|
||||
} else {
|
||||
total++;
|
||||
p++;
|
||||
}
|
||||
}
|
||||
|
||||
char* result = (char*)bux_alloc(total + 1);
|
||||
char* w = result;
|
||||
p = pattern;
|
||||
while (*p) {
|
||||
if (*p == '{' && p[1] >= '0' && p[1] <= '7' && p[2] == '}') {
|
||||
int idx = p[1] - '0';
|
||||
if (args[idx]) {
|
||||
size_t len = strlen(args[idx]);
|
||||
memcpy(w, args[idx], len);
|
||||
w += len;
|
||||
}
|
||||
p += 3;
|
||||
} else {
|
||||
*w++ = *p++;
|
||||
}
|
||||
}
|
||||
*w = '\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
/* File I/O — read entire file into string */
|
||||
char* bux_read_file(const char* path) {
|
||||
if (!path) return NULL;
|
||||
FILE* f = fopen(path, "rb");
|
||||
if (!f) return NULL;
|
||||
fseek(f, 0, SEEK_END);
|
||||
long size = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
char* buf = (char*)bux_alloc((size_t)size + 1);
|
||||
size_t read = fread(buf, 1, (size_t)size, f);
|
||||
fclose(f);
|
||||
buf[read] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* File I/O — write string to file */
|
||||
int bux_write_file(const char* path, const char* content) {
|
||||
if (!path || !content) return 0;
|
||||
FILE* f = fopen(path, "wb");
|
||||
if (!f) return 0;
|
||||
size_t len = strlen(content);
|
||||
size_t written = fwrite(content, 1, len, f);
|
||||
fclose(f);
|
||||
return written == len ? 1 : 0;
|
||||
}
|
||||
|
||||
/* File I/O — check if file exists */
|
||||
int bux_file_exists(const char* path) {
|
||||
if (!path) return 0;
|
||||
FILE* f = fopen(path, "rb");
|
||||
if (f) {
|
||||
fclose(f);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Path operations */
|
||||
char* bux_path_join(const char* a, const char* b) {
|
||||
if (!a && !b) {
|
||||
char* empty = (char*)bux_alloc(1);
|
||||
empty[0] = '\0';
|
||||
return empty;
|
||||
}
|
||||
if (!a) {
|
||||
unsigned int len = (unsigned int)strlen(b);
|
||||
char* result = (char*)bux_alloc(len + 1);
|
||||
memcpy(result, b, len + 1);
|
||||
return result;
|
||||
}
|
||||
if (!b) {
|
||||
unsigned int len = (unsigned int)strlen(a);
|
||||
char* result = (char*)bux_alloc(len + 1);
|
||||
memcpy(result, a, len + 1);
|
||||
return result;
|
||||
}
|
||||
unsigned int len_a = (unsigned int)strlen(a);
|
||||
unsigned int len_b = (unsigned int)strlen(b);
|
||||
int need_sep = (len_a > 0 && a[len_a-1] != '/') ? 1 : 0;
|
||||
unsigned int total = len_a + (need_sep ? 1 : 0) + len_b;
|
||||
char* result = (char*)bux_alloc(total + 1);
|
||||
memcpy(result, a, len_a);
|
||||
if (need_sep) result[len_a] = '/';
|
||||
memcpy(result + len_a + (need_sep ? 1 : 0), b, len_b);
|
||||
result[total] = '\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
char* bux_path_parent(const char* path) {
|
||||
if (!path) {
|
||||
char* empty = (char*)bux_alloc(1);
|
||||
empty[0] = '\0';
|
||||
return empty;
|
||||
}
|
||||
int len = (int)strlen(path);
|
||||
while (len > 0 && path[len-1] == '/') len--;
|
||||
while (len > 0 && path[len-1] != '/') len--;
|
||||
while (len > 0 && path[len-1] == '/') len--;
|
||||
if (len == 0) {
|
||||
char* dot = (char*)bux_alloc(2);
|
||||
dot[0] = '.';
|
||||
dot[1] = '\0';
|
||||
return dot;
|
||||
}
|
||||
char* result = (char*)bux_alloc((unsigned int)len + 1);
|
||||
memcpy(result, path, (unsigned int)len);
|
||||
result[len] = '\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
char* bux_path_ext(const char* path) {
|
||||
if (!path) {
|
||||
char* empty = (char*)bux_alloc(1);
|
||||
empty[0] = '\0';
|
||||
return empty;
|
||||
}
|
||||
const char* dot = strrchr(path, '.');
|
||||
if (!dot) {
|
||||
char* empty = (char*)bux_alloc(1);
|
||||
empty[0] = '\0';
|
||||
return empty;
|
||||
}
|
||||
const char* slash = strrchr(path, '/');
|
||||
if (slash && slash > dot) {
|
||||
char* empty = (char*)bux_alloc(1);
|
||||
empty[0] = '\0';
|
||||
return empty;
|
||||
}
|
||||
unsigned int len = (unsigned int)strlen(dot);
|
||||
char* result = (char*)bux_alloc(len + 1);
|
||||
memcpy(result, dot, len + 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Hash function (djb2) over raw bytes — for generic key types */
|
||||
unsigned int bux_hash_bytes(const void* ptr, size_t size) {
|
||||
if (!ptr) return 0;
|
||||
unsigned int hash = 5381;
|
||||
const unsigned char* bytes = (const unsigned char*)ptr;
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
hash = ((hash << 5) + hash) + bytes[i]; /* hash * 33 + byte */
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
/* Byte equality check — for generic key comparison */
|
||||
int bux_mem_eq(const void* a, const void* b, size_t size) {
|
||||
if (a == b) return 1;
|
||||
if (!a || !b) return 0;
|
||||
return memcmp(a, b, size) == 0;
|
||||
}
|
||||
|
||||
/* Hash function (djb2) for string keys */
|
||||
unsigned int bux_hash_string(const char* s) {
|
||||
unsigned int hash = 5381;
|
||||
|
||||
Reference in New Issue
Block a user