Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
```
4 changes: 2 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`)

Expand Down
131 changes: 131 additions & 0 deletions crates/ns_io/src/print.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<NsString>,
}

#[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);
}
33 changes: 33 additions & 0 deletions examples/14_string_interpolation.c
Original file line number Diff line number Diff line change
@@ -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;
}
133 changes: 110 additions & 23 deletions include/ns_print.h
Original file line number Diff line number Diff line change
Expand Up @@ -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, \
Expand All @@ -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, \
Expand All @@ -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
Expand Down