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
63 changes: 63 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
resolver = "2"
members = [
"crates/ns_io",
"crates/ns_string"
"crates/ns_string",
"crates/ns_error"
# "crates/ns_data"
]

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ compiling the C examples, and linking everything together.
- [x] Separate `print` and `println` functions
- [x] Cargo Workspace Architecture
- [x] User Input via `ns_read()`
- [x] Custom Error types (`ns_error_t`)
- [ ] Printing Variables + Strings (Interpolation/Formatting)

## Quick Start
Expand Down
11 changes: 11 additions & 0 deletions crates/ns_error/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "ns_error"
version.workspace = true
edition.workspace = true
authors.workspace = true

[dependencies]
thiserror = "2.0.18"

[lib]
crate-type = ["lib", "staticlib", "cdylib"]
53 changes: 53 additions & 0 deletions crates/ns_error/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use std::os::raw::c_char;
use thiserror::Error;

#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Error)]
pub enum NsError {
#[error("Success")]
Success = 0,

// Catch-all (Similar to python's "except Exception")
#[error("Unknown Error")]
Any = 1,

// ns_io errors
#[error("I/O Error: Failed to read input")]
IoReadFailed = 10,

#[error("I/O Error: Failed to write input")]
IoWriteFailed = 11,

// Invalid input
#[error("I/O Error: Invalid Input format")]
InvalidInput = 12,

//ns_string errors
#[error("String Error: Memory Allocation failed")]
StringAllocFailed = 20,

#[error("String Error: Invalid UTF-8 sequence detected")]
StringInvalidUtf8 = 21,

#[error("Error: Index out of bounds")]
IndexOutOfBounds = 22,
}

// Helper functions
// This is added to help C easily print exactly what went wrong

#[unsafe(no_mangle)]
pub extern "C" fn ns_error_message(err: NsError) -> *const c_char {
let msg = match err {
NsError::Success => c"Success",
NsError::Any => c"Unknown Error",
NsError::IoReadFailed => c"I/O Error: Failed to read input",
NsError::IoWriteFailed => c"I/O Error: Failed to write input",
NsError::InvalidInput => c"I/O Error: Invalid Input format",
NsError::StringAllocFailed => c"String Error: Memory allocation failed",
NsError::StringInvalidUtf8 => c"String Error: Invalid UTF-8 sequence detected",
NsError::IndexOutOfBounds => c"Error: Index Out of Bounds",
};

msg.as_ptr()
}
1 change: 1 addition & 0 deletions crates/ns_io/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ authors.workspace = true
crate-type = ["cdylib", "staticlib"]

[dependencies]
ns_error = { path = "../ns_error" }
ns_string = { path = "../ns_string" }
114 changes: 71 additions & 43 deletions crates/ns_io/src/input.rs
Original file line number Diff line number Diff line change
@@ -1,74 +1,102 @@
use ns_error::NsError;
use std::ffi::{c_double, c_float, c_int};
use std::io::{self};

// Helper function to read a line from stdin safely
fn read_line_buffer() -> String {
fn read_line_buffer() -> Result<String, NsError> {
let mut buffer = String::new();

// Ignore error for now (eg:Closed pipe)
let _ = io::stdin().read_line(&mut buffer);
buffer.trim().to_string()
// If reading from stdin completely fails (e.g., broken pipe), return the specific error
if io::stdin().read_line(&mut buffer).is_err() {
return Err(NsError::IoReadFailed);
}

Ok(buffer.trim().to_string())
}

// Read Integer
#[unsafe(no_mangle)]
/// Read Integer
///
/// # Safety
///
/// This function reads an integer input
pub unsafe extern "C" fn ns_read_int(ptr: *mut c_int) {
/// This function is unsafe because it dereferences a raw pointer provided by C.
/// The caller must ensure that `ptr` is valid, properly aligned, and points to
/// initialized memory of type `c_int`. The function performs a null check, but
/// cannot guarantee the pointer is not dangling.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ns_read_int(ptr: *mut c_int) -> NsError {
if ptr.is_null() {
return;
return NsError::Any; // Prevents segfaults, flags an error
}

let input = read_line_buffer();

// Try to parse. If it fails, default to 0
let val: c_int = input.parse().unwrap_or(0);

// Unsafe: Write value to C memory address
unsafe {
*ptr = val;
match read_line_buffer() {
Ok(input) => {
// Match the parse result instead of blindly unwrapping!
match input.parse::<c_int>() {
Ok(val) => {
unsafe {
*ptr = val;
}
NsError::Success
}
Err(_) => NsError::InvalidInput,
}
}
Err(e) => e,
}
}

// Read float
#[unsafe(no_mangle)]
/// Read float
///
/// # Safety
///
/// This function reads a floating point input
pub unsafe extern "C" fn ns_read_float(ptr: *mut c_float) {
/// This function is unsafe because it dereferences a raw pointer provided by C.
/// The caller must ensure that `ptr` is valid, properly aligned, and points to
/// initialized memory of type `c_float`. The function performs a null check, but
/// cannot guarantee the pointer is not dangling.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ns_read_float(ptr: *mut c_float) -> NsError {
if ptr.is_null() {
return;
return NsError::Any;
}

let input = read_line_buffer();

// Try to parse. If it fails, default to 0
let val: c_float = input.parse().unwrap_or(0.0);

// Unsafe: Write value to C memory address
unsafe {
*ptr = val;
match read_line_buffer() {
Ok(input) => match input.parse::<c_float>() {
Ok(val) => {
unsafe {
*ptr = val;
}
NsError::Success
}
Err(_) => NsError::InvalidInput,
},
Err(e) => e,
}
}

// Read double
#[unsafe(no_mangle)]
/// Read double
///
/// # Safety
///
/// This function reads a double input
pub unsafe extern "C" fn ns_read_double(ptr: *mut c_double) {
/// This function is unsafe because it dereferences a raw pointer provided by C.
/// The caller must ensure that `ptr` is valid, properly aligned, and points to
/// initialized memory of type `c_double`. The function performs a null check, but
/// cannot guarantee the pointer is not dangling.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ns_read_double(ptr: *mut c_double) -> NsError {
if ptr.is_null() {
return;
return NsError::Any;
}

let input = read_line_buffer();

// Try to parse. If it fails, default to 0
let val: c_double = input.parse().unwrap_or(0.0);

// Unsafe: Write value to C memory address
unsafe {
*ptr = val;
match read_line_buffer() {
Ok(input) => match input.parse::<c_double>() {
Ok(val) => {
unsafe {
*ptr = val;
}
NsError::Success
}
Err(_) => NsError::InvalidInput,
},
Err(e) => e,
}
}
43 changes: 43 additions & 0 deletions examples/06_errors.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#include "../include/ns.h"
#include "../include/ns_error.h"

int main() {
ns_error_t err;

int age;

ns_println("----NextStd Error handling example---");
ns_println("Try typing a valid number, or the text to see a safe fallback");
ns_print("Enter your age: ");

NS_TRY(err, ns_read(&age)) {
ns_print("Success, you entered: ");
ns_println(age);
}
NS_EXCEPT(err, NS_ERROR_INVALID_INPUT) {
ns_println("That is not a valid number! Please enter digits only.");
}
NS_EXCEPT(err, NS_ERROR_IO_READ) {
ns_println("Failed to read from standard input");
}
NS_EXCEPT(err, NS_ERROR_ANY) {
ns_print("An error occurred: ");
ns_println(ns_error_message(err));
}

ns_println("");
ns_println("----Testing Null pointer safety----");
ns_println("Attempting to read a NULL pointer");

int* ptr = 0;

NS_TRY(err, ns_read(ptr)) {
ns_println("CRITICAL FAILURE: This should not print!");
}
NS_EXCEPT(err, NS_ERROR_ANY) {
ns_print("Safely caught the bad pointer! Error Message: ");
ns_println(ns_error_message(err));
}

return 0;
}
40 changes: 40 additions & 0 deletions include/ns_error.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#ifndef NS_ERROR_H
#define NS_ERROR_H

#ifdef __cplusplus
extern "C" {
#endif

typedef enum {
NS_SUCCESS = 0,

// Catch all
NS_ERROR_ANY = 1,

//ns_io errors
NS_ERROR_IO_READ = 10,
NS_ERROR_IO_WRITE = 11,
NS_ERROR_INVALID_INPUT = 12,

// ns_string errors
NS_ERROR_STRING_ALLOC = 20,
NS_ERROR_STRING_UTF8 = 21,

NS_ERROR_OUT_OF_BOUNDS = 22,
} ns_error_t;

const char* ns_error_message(ns_error_t err);

#define NS_TRY(err_var, expr) \
if (((err_var) = (expr)) == NS_SUCCESS)

#define NS_EXCEPT(err_var, err_type) \
else if ((err_var) == (err_type) || (err_type) == NS_ERROR_ANY)

#ifdef __cplusplus

}

#endif // __cplusplus

#endif // !NS_ERROR_H
Loading