add: fib sample

This commit is contained in:
2026-03-12 22:19:42 -07:00
parent 073c86d784
commit 31a6c8b91b
7 changed files with 756 additions and 46 deletions

View File

@@ -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]