diff --git a/BUILD.md b/BUILD.md index 3aa735a..07a01ab 100644 --- a/BUILD.md +++ b/BUILD.md @@ -7,7 +7,7 @@ You will need the following tools installed to build `NextStd`: 1. **Rust & Cargo** (For compiling the backend): ```bash - curl --proto '=https' --tlsv1.2 -sSf [https://sh.rustup.rs](https://sh.rustup.rs) | sh + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` 2. **GCC or Clang** (For the C front-end and macro expansion) @@ -54,10 +54,13 @@ are separated into their own modules to prevent core header bloat. ├── Cargo.toml ├── CHANGELOG.md ├── cliff.toml +├── CODE_OF_CONDUCT.md +├── CONTRIBUTING.md ├── crates │ ├── ns_data │ │ ├── Cargo.toml │ │ └── src +│ │ ├── hashmap.rs │ │ ├── lib.rs │ │ └── vec.rs │ ├── ns_error @@ -83,9 +86,15 @@ are separated into their own modules to prevent core header bloat. │ ├── 06_errors.c │ ├── 07_colors.c │ ├── 08_string_error.c -│ └── 09_vectors.c +│ ├── 09_vectors.c +│ ├── 10_hashmap.c +│ ├── 11_underline.c +│ ├── 12_string_length.c +│ ├── 13_read_ns_string.c +│ └── 14_string_interpolation.c ├── include │ ├── data_structures +│ │ ├── ns_hashmap.h │ │ └── ns_vec.h │ ├── ns.h │ ├── ns_color.h @@ -97,6 +106,7 @@ are separated into their own modules to prevent core header bloat. ├── LICENSE ├── Makefile ├── README.md +├── ROADMAP.md ├── rumdl.toml └── USAGE.md ``` diff --git a/ROADMAP.md b/ROADMAP.md index 90d6971..e7462b3 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -31,9 +31,9 @@ for production use. These features will finalize the core `ns_io` and `ns_string` modules. -* [ ] **String Interpolation & Formatting:** Introduce Python/Rust-style string +* [x] **String Interpolation & Formatting:** Introduce Python/Rust-style string formatting to eliminate the need for `sprintf`. - * *Concept:* `ns_println("Value: {val}");` + * *Concept:* `ns_println("Value: {}", val);` ## Phase 2: Process Execution (`ns_cmd` & `ns_process`) diff --git a/crates/ns_io/src/print.rs b/crates/ns_io/src/print.rs index 8aff50a..50e915b 100644 --- a/crates/ns_io/src/print.rs +++ b/crates/ns_io/src/print.rs @@ -1,6 +1,7 @@ use ns_string::NsString; use std::ffi::CStr; use std::io::{self, Write}; +use std::mem::ManuallyDrop; use std::os::raw::c_char; // No Newline functions @@ -166,3 +167,133 @@ pub extern "C" fn ns_print_size_t(val: usize) { pub extern "C" fn ns_println_size_t(val: usize) { println!("{}", val); } + +// String Interpolation + +#[repr(C)] +pub enum NsTypeTag { + Int = 0, + Float, + Double, + Bool, + SizeT, + CStr, + NsString, +} + +#[repr(C)] +pub union NsAnyData { + pub v_int: i32, + pub v_float: f32, + pub v_double: f64, + pub v_bool: bool, + pub v_size_t: usize, + pub v_cstr: *const c_char, + pub v_ns_string: ManuallyDrop, +} + +#[repr(C)] +pub struct NsAnyT { + pub tag: NsTypeTag, + pub data: NsAnyData, +} + +// Formatter Engine +// Takes the format string and array of arguments, and builds the final string +fn format_any(fmt_str: &str, args_slice: &[NsAnyT]) -> String { + let mut result = String::new(); + let mut arg_idx = 0; + + // String splitting + let mut parts = fmt_str.split("{}"); + + if let Some(first) = parts.next() { + result.push_str(first); + } + + for part in parts { + if arg_idx < args_slice.len() { + let arg = &args_slice[arg_idx]; + + let formatted_val = unsafe { + match arg.tag { + NsTypeTag::Int => arg.data.v_int.to_string(), + NsTypeTag::Float => arg.data.v_float.to_string(), + NsTypeTag::Double => arg.data.v_double.to_string(), + NsTypeTag::Bool => { + if arg.data.v_bool { + "true".to_string() + } else { + "false".to_string() + } + } + NsTypeTag::SizeT => arg.data.v_size_t.to_string(), + NsTypeTag::CStr => { + if arg.data.v_cstr.is_null() { + "(null)".to_string() + } else { + CStr::from_ptr(arg.data.v_cstr) + .to_string_lossy() + .into_owned() + } + } + NsTypeTag::NsString => { + let val = &*arg.data.v_ns_string; + let bytes: &[u8] = if val.is_heap { + std::slice::from_raw_parts(val.data.heap.ptr as *const u8, val.len) + } else { + std::slice::from_raw_parts(val.data.inline_data.as_ptr(), val.len) + }; + String::from_utf8_lossy(bytes).into_owned() + } + } + }; + + result.push_str(&formatted_val); + arg_idx += 1; + } else { + result.push_str("{}"); + } + result.push_str(part); + } + + result +} + +// FFI boundary functions +#[unsafe(no_mangle)] +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub extern "C" fn ns_print_fmt_c(fmt: *const c_char, args: *const NsAnyT, num_args: usize) { + if fmt.is_null() { + return; + } + + let c_str = unsafe { CStr::from_ptr(fmt) }; + + let fmt_str = c_str.to_string_lossy(); + + let args_slice = unsafe { std::slice::from_raw_parts(args, num_args) }; + + let final_output = format_any(&fmt_str, args_slice); + + print!("{}", final_output); + io::stdout().flush().unwrap(); +} + +#[unsafe(no_mangle)] +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub extern "C" fn ns_println_fmt_c(fmt: *const c_char, args: *const NsAnyT, num_args: usize) { + if fmt.is_null() { + return; + } + + let c_str = unsafe { CStr::from_ptr(fmt) }; + + let fmt_str = c_str.to_string_lossy(); + + let args_slice = unsafe { std::slice::from_raw_parts(args, num_args) }; + + let final_output = format_any(&fmt_str, args_slice); + + println!("{}", final_output); +} diff --git a/examples/14_string_interpolation.c b/examples/14_string_interpolation.c new file mode 100644 index 0000000..e135767 --- /dev/null +++ b/examples/14_string_interpolation.c @@ -0,0 +1,33 @@ +#include "../include/ns.h" +#include "../include/ns_color.h" + +int main(void) +{ + ns_println("NextStd String Interpolation"); + + // Mixed Types + int rewatch_count = 5; + const char* anime = "Frieren: beyond Journey's end"; + ns_println("My Favorite anime is {} and I have watched it {} times", anime, rewatch_count); + + float temp = 45.2f; + _Bool is_active = 1; + + ns_println("System Active: {}, CPU Temperature: {}", is_active, temp); + + ns_error_t err; + ns_string os_name; + + NS_TRY(err, ns_string_new(&os_name, "Endeavour OS")) { + ns_println("I am currently running {} on my machine", os_name); + ns_string_free(&os_name); + } NS_EXCEPT(err, NS_ERROR_ANY) { + ns_println(ns_error_message(err)); + } + + // safety Test (This normally causes a segfault in C) + ns_println("\n----Safety Test-----"); + ns_println("Expected 3 vars, gave 2: {}, {}, {}", 100, 200); + + return 0; +} diff --git a/include/ns_print.h b/include/ns_print.h index ee1ff95..a227d46 100644 --- a/include/ns_print.h +++ b/include/ns_print.h @@ -8,27 +8,85 @@ extern "C" { #endif - // -----Printing functions------ - // No Newline - void ns_print_int(int val); - void ns_print_float(float val); - void ns_print_double(double val); - void ns_print_bool(_Bool val); - void ns_print_size_t(size_t val); - void ns_print_string(const char* val); - void ns_print_ns_string(ns_string val); - - // Newline - void ns_println_int(int val); - void ns_println_float(float val); - void ns_println_double(double val); - void ns_println_bool(_Bool val); - void ns_println_size_t(size_t val); - void ns_println_string(const char* val); - void ns_println_ns_string(ns_string val); - - // Generic Macro (No Newline) -#define ns_print(x) _Generic((x), \ +// Formatting & Interpolation Types + +// 1. The Tag: Identifies what type is stored in the union +typedef enum ns_type_tag { + NS_TYPE_INT, + NS_TYPE_FLOAT, + NS_TYPE_DOUBLE, + NS_TYPE_BOOL, + NS_TYPE_SIZE_T, + NS_TYPE_CSTR, + NS_TYPE_NS_STRING +} ns_type_tag; + +// 2. The Data: A union that is exactly as large as its largest member +typedef union ns_any_data { + int v_int; + float v_float; + double v_double; + _Bool v_bool; + size_t v_size_t; + const char* v_cstr; + ns_string v_ns_string; +} ns_any_data; + +// 3. The Package: A single struct we can pass to Rust safely +typedef struct ns_any_t { + ns_type_tag tag; + ns_any_data data; +} ns_any_t; + +// 4. Constructor Functions (Shields from strict _Generic type-checking and nested designator warnings) +static inline ns_any_t ns_any_from_int(int v) { ns_any_t a; a.tag = NS_TYPE_INT; a.data.v_int = v; return a; } +static inline ns_any_t ns_any_from_float(float v) { ns_any_t a; a.tag = NS_TYPE_FLOAT; a.data.v_float = v; return a; } +static inline ns_any_t ns_any_from_double(double v) { ns_any_t a; a.tag = NS_TYPE_DOUBLE; a.data.v_double = v; return a; } +static inline ns_any_t ns_any_from_bool(_Bool v) { ns_any_t a; a.tag = NS_TYPE_BOOL; a.data.v_bool = v; return a; } +static inline ns_any_t ns_any_from_size_t(size_t v) { ns_any_t a; a.tag = NS_TYPE_SIZE_T; a.data.v_size_t = v; return a; } +static inline ns_any_t ns_any_from_cstr(const char* v) { ns_any_t a; a.tag = NS_TYPE_CSTR; a.data.v_cstr = v; return a; } +static inline ns_any_t ns_any_from_ns_string(ns_string v) { ns_any_t a; a.tag = NS_TYPE_NS_STRING; a.data.v_ns_string = v; return a; } + +// 5. The Magic Converter: Selects the correct constructor function +#define ns_to_any(x) _Generic((x), \ + int: ns_any_from_int, \ + float: ns_any_from_float, \ + double: ns_any_from_double, \ + _Bool: ns_any_from_bool, \ + size_t: ns_any_from_size_t, \ + char*: ns_any_from_cstr, \ + const char*: ns_any_from_cstr, \ + ns_string: ns_any_from_ns_string \ +)(x) + +// Rust FFI Functions + +// Formatted printing (Our new Rust functions) +void ns_print_fmt_c(const char* fmt, ns_any_t* args, size_t num_args); +void ns_println_fmt_c(const char* fmt, ns_any_t* args, size_t num_args); + +// Single-variable printing (No Newline) +void ns_print_int(int val); +void ns_print_float(float val); +void ns_print_double(double val); +void ns_print_bool(_Bool val); +void ns_print_size_t(size_t val); +void ns_print_string(const char* val); +void ns_print_ns_string(ns_string val); + +// Single-variable printing (Newline) +void ns_println_int(int val); +void ns_println_float(float val); +void ns_println_double(double val); +void ns_println_bool(_Bool val); +void ns_println_size_t(size_t val); +void ns_println_string(const char* val); +void ns_println_ns_string(ns_string val); + +// The Macro Magic (Overloading by Argument Count) + +// 1. Single argument dispatcher (The original generic macros) +#define ns_print_1(x) _Generic((x), \ int: ns_print_int, \ float: ns_print_float, \ double: ns_print_double, \ @@ -39,8 +97,7 @@ extern "C" { ns_string: ns_print_ns_string \ )(x) - // Generic Macro (With newline) -#define ns_println(x) _Generic((x), \ +#define ns_println_1(x) _Generic((x), \ int: ns_println_int, \ float: ns_println_float, \ double: ns_println_double, \ @@ -51,6 +108,36 @@ extern "C" { ns_string: ns_println_ns_string \ )(x) +// 2. Formatted argument dispatchers (Supports up to 8 variables formatted at once) +// ns_print (No newline) +#define ns_print_2(fmt, a) ns_print_fmt_c(fmt, (ns_any_t[]){ns_to_any(a)}, 1) +#define ns_print_3(fmt, a, b) ns_print_fmt_c(fmt, (ns_any_t[]){ns_to_any(a), ns_to_any(b)}, 2) +#define ns_print_4(fmt, a, b, c) ns_print_fmt_c(fmt, (ns_any_t[]){ns_to_any(a), ns_to_any(b), ns_to_any(c)}, 3) +#define ns_print_5(fmt, a, b, c, d) ns_print_fmt_c(fmt, (ns_any_t[]){ns_to_any(a), ns_to_any(b), ns_to_any(c), ns_to_any(d)}, 4) +#define ns_print_6(fmt, a, b, c, d, e) ns_print_fmt_c(fmt, (ns_any_t[]){ns_to_any(a), ns_to_any(b), ns_to_any(c), ns_to_any(d), ns_to_any(e)}, 5) +#define ns_print_7(fmt, a, b, c, d, e, f) ns_print_fmt_c(fmt, (ns_any_t[]){ns_to_any(a), ns_to_any(b), ns_to_any(c), ns_to_any(d), ns_to_any(e), ns_to_any(f)}, 6) +#define ns_print_8(fmt, a, b, c, d, e, f, g) ns_print_fmt_c(fmt, (ns_any_t[]){ns_to_any(a), ns_to_any(b), ns_to_any(c), ns_to_any(d), ns_to_any(e), ns_to_any(f), ns_to_any(g)}, 7) +#define ns_print_9(fmt, a, b, c, d, e, f, g, h) ns_print_fmt_c(fmt, (ns_any_t[]){ns_to_any(a), ns_to_any(b), ns_to_any(c), ns_to_any(d), ns_to_any(e), ns_to_any(f), ns_to_any(g), ns_to_any(h)}, 8) + +// ns_println (With newline) +#define ns_println_2(fmt, a) ns_println_fmt_c(fmt, (ns_any_t[]){ns_to_any(a)}, 1) +#define ns_println_3(fmt, a, b) ns_println_fmt_c(fmt, (ns_any_t[]){ns_to_any(a), ns_to_any(b)}, 2) +#define ns_println_4(fmt, a, b, c) ns_println_fmt_c(fmt, (ns_any_t[]){ns_to_any(a), ns_to_any(b), ns_to_any(c)}, 3) +#define ns_println_5(fmt, a, b, c, d) ns_println_fmt_c(fmt, (ns_any_t[]){ns_to_any(a), ns_to_any(b), ns_to_any(c), ns_to_any(d)}, 4) +#define ns_println_6(fmt, a, b, c, d, e) ns_println_fmt_c(fmt, (ns_any_t[]){ns_to_any(a), ns_to_any(b), ns_to_any(c), ns_to_any(d), ns_to_any(e)}, 5) +#define ns_println_7(fmt, a, b, c, d, e, f) ns_println_fmt_c(fmt, (ns_any_t[]){ns_to_any(a), ns_to_any(b), ns_to_any(c), ns_to_any(d), ns_to_any(e), ns_to_any(f)}, 6) +#define ns_println_8(fmt, a, b, c, d, e, f, g) ns_println_fmt_c(fmt, (ns_any_t[]){ns_to_any(a), ns_to_any(b), ns_to_any(c), ns_to_any(d), ns_to_any(e), ns_to_any(f), ns_to_any(g)}, 7) +#define ns_println_9(fmt, a, b, c, d, e, f, g, h) ns_println_fmt_c(fmt, (ns_any_t[]){ns_to_any(a), ns_to_any(b), ns_to_any(c), ns_to_any(d), ns_to_any(e), ns_to_any(f), ns_to_any(g), ns_to_any(h)}, 8) + +// 3. The Router Macros +#define NS_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, NAME, ...) NAME + +#define ns_print(...) \ + NS_GET_MACRO(__VA_ARGS__, ns_print_9, ns_print_8, ns_print_7, ns_print_6, ns_print_5, ns_print_4, ns_print_3, ns_print_2, ns_print_1)(__VA_ARGS__) + +#define ns_println(...) \ + NS_GET_MACRO(__VA_ARGS__, ns_println_9, ns_println_8, ns_println_7, ns_println_6, ns_println_5, ns_println_4, ns_println_3, ns_println_2, ns_println_1)(__VA_ARGS__) + #ifdef __cplusplus } #endif // !__cplusplus