add: fib sample
This commit is contained in:
118
rpgrt/src/lib.rs
118
rpgrt/src/lib.rs
@@ -12,6 +12,8 @@
|
||||
//! | `rpg_dsply_i64` | `(n: i64)` | Display a signed 64-bit integer |
|
||||
//! | `rpg_dsply_f64` | `(f: f64)` | Display a double-precision float |
|
||||
//! | `rpg_halt` | `(code: i32)` | Abnormal program termination |
|
||||
//! | `rpg_char_i64` | `(n: i64) -> *const c_char` | Format integer as null-term C string |
|
||||
//! | `rpg_concat` | `(a: *const c_char, b: *const c_char) -> *const c_char` | Concatenate two C strings |
|
||||
//!
|
||||
//! ## Building
|
||||
//!
|
||||
@@ -44,10 +46,23 @@
|
||||
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::cell::RefCell;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::io::{self, Write};
|
||||
use std::slice;
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Thread-local scratch buffers used by rpg_char_i64 / rpg_concat
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
thread_local! {
|
||||
/// Backing store for the most recent `rpg_char_i64` result.
|
||||
static CHAR_BUF: RefCell<CString> = RefCell::new(CString::new("").unwrap());
|
||||
|
||||
/// Backing store for the most recent `rpg_concat` result.
|
||||
static CONCAT_BUF: RefCell<CString> = RefCell::new(CString::new("").unwrap());
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// rpg_dsply — display a fixed-length character field
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
@@ -151,6 +166,66 @@ pub extern "C" fn rpg_dsply_f64(f: f64) {
|
||||
/// Maps roughly to the IBM i concept of an *unhandled exception* ending the
|
||||
/// job.
|
||||
#[no_mangle]
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// rpg_char_i64 — convert a 64-bit integer to a C string (%CHAR built-in)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Format `n` as a decimal C string and return a pointer to a thread-local
|
||||
/// buffer holding the result.
|
||||
///
|
||||
/// The returned pointer is valid until the next call to `rpg_char_i64` on the
|
||||
/// same thread. Callers must not free it.
|
||||
///
|
||||
/// This implements the RPG IV `%CHAR(numeric-expression)` built-in function.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rpg_char_i64(n: i64) -> *const std::os::raw::c_char {
|
||||
let s = CString::new(n.to_string()).unwrap_or_else(|_| CString::new("0").unwrap());
|
||||
CHAR_BUF.with(|cell| {
|
||||
*cell.borrow_mut() = s;
|
||||
cell.borrow().as_ptr()
|
||||
})
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// rpg_concat — concatenate two null-terminated C strings ('+' on char)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Concatenate two null-terminated C strings and return a pointer to a
|
||||
/// thread-local buffer holding the result.
|
||||
///
|
||||
/// The returned pointer is valid until the next call to `rpg_concat` on the
|
||||
/// same thread. Callers must not free it.
|
||||
///
|
||||
/// This implements the RPG IV `+` operator when both operands are character
|
||||
/// expressions.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Both `a` and `b` must be valid null-terminated C strings (or null pointers,
|
||||
/// which are treated as empty strings).
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rpg_concat(
|
||||
a: *const std::os::raw::c_char,
|
||||
b: *const std::os::raw::c_char,
|
||||
) -> *const std::os::raw::c_char {
|
||||
let sa = if a.is_null() {
|
||||
std::borrow::Cow::Borrowed("")
|
||||
} else {
|
||||
unsafe { CStr::from_ptr(a).to_string_lossy() }
|
||||
};
|
||||
let sb = if b.is_null() {
|
||||
std::borrow::Cow::Borrowed("")
|
||||
} else {
|
||||
unsafe { CStr::from_ptr(b).to_string_lossy() }
|
||||
};
|
||||
let joined = format!("{}{}", sa, sb);
|
||||
let cs = CString::new(joined).unwrap_or_else(|_| CString::new("").unwrap());
|
||||
CONCAT_BUF.with(|cell| {
|
||||
*cell.borrow_mut() = cs;
|
||||
cell.borrow().as_ptr()
|
||||
})
|
||||
}
|
||||
|
||||
pub extern "C" fn rpg_halt(code: i32) {
|
||||
eprintln!("RPG program halted with code {}", code);
|
||||
std::process::exit(code);
|
||||
@@ -374,6 +449,47 @@ fn rtrim_spaces(bytes: &[u8]) -> &[u8] {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// ── rpg_dsply ────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn char_i64_positive() {
|
||||
let ptr = rpg_char_i64(42);
|
||||
let s = unsafe { CStr::from_ptr(ptr).to_str().unwrap() };
|
||||
assert_eq!(s, "42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn char_i64_zero() {
|
||||
let ptr = rpg_char_i64(0);
|
||||
let s = unsafe { CStr::from_ptr(ptr).to_str().unwrap() };
|
||||
assert_eq!(s, "0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn char_i64_negative() {
|
||||
let ptr = rpg_char_i64(-7);
|
||||
let s = unsafe { CStr::from_ptr(ptr).to_str().unwrap() };
|
||||
assert_eq!(s, "-7");
|
||||
}
|
||||
|
||||
// ── rpg_concat ───────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn concat_two_strings() {
|
||||
let a = CString::new("Hello, ").unwrap();
|
||||
let b = CString::new("World!").unwrap();
|
||||
let ptr = unsafe { rpg_concat(a.as_ptr(), b.as_ptr()) };
|
||||
let s = unsafe { CStr::from_ptr(ptr).to_str().unwrap() };
|
||||
assert_eq!(s, "Hello, World!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concat_null_pointers() {
|
||||
let ptr = unsafe { rpg_concat(std::ptr::null(), std::ptr::null()) };
|
||||
let s = unsafe { CStr::from_ptr(ptr).to_str().unwrap() };
|
||||
assert_eq!(s, "");
|
||||
}
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user