Compare commits
1 Commits
8e36afbf67
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 832dae44fb |
@@ -11,6 +11,8 @@
|
|||||||
//! | `rpg_dsply_cstr` | `(ptr: *const c_char)` | Display a null-terminated C string |
|
//! | `rpg_dsply_cstr` | `(ptr: *const c_char)` | Display a null-terminated C string |
|
||||||
//! | `rpg_dsply_i64` | `(n: i64)` | Display a signed 64-bit integer |
|
//! | `rpg_dsply_i64` | `(n: i64)` | Display a signed 64-bit integer |
|
||||||
//! | `rpg_dsply_f64` | `(f: f64)` | Display a double-precision float |
|
//! | `rpg_dsply_f64` | `(f: f64)` | Display a double-precision float |
|
||||||
|
//! | `rpg_dsply_read` | `(prompt: *const c_char, response: *mut i64)` | Display null-term prompt & read i64 |
|
||||||
|
//! | `rpg_dsply_read_len`| `(ptr: *const u8, len: i64, response: *mut i64)` | Display length-delimited prompt & read i64 |
|
||||||
//! | `rpg_halt` | `(code: i32)` | Abnormal program termination |
|
//! | `rpg_halt` | `(code: i32)` | Abnormal program termination |
|
||||||
//! | `rpg_char_i64` | `(n: i64) -> *const c_char` | Format integer as null-term C string |
|
//! | `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 |
|
//! | `rpg_concat` | `(a: *const c_char, b: *const c_char) -> *const c_char` | Concatenate two C strings |
|
||||||
@@ -209,6 +211,59 @@ pub unsafe extern "C" fn rpg_dsply_read(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// rpg_dsply_read_len — display a length-delimited prompt and read a response
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Three-operand DSPLY where the prompt is a fixed-length (or VarChar) field
|
||||||
|
/// passed as `(ptr, len)` rather than a null-terminated C string.
|
||||||
|
///
|
||||||
|
/// This is called when the prompt operand is a `Char` or `VarChar` variable —
|
||||||
|
/// those are stored as raw byte buffers with no null terminator, so
|
||||||
|
/// `rpg_dsply_read` (which calls `CStr::from_ptr`) cannot be used safely.
|
||||||
|
///
|
||||||
|
/// Trailing ASCII spaces are stripped from the prompt before display, matching
|
||||||
|
/// the behaviour of `rpg_dsply`.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// * `ptr` must be valid for at least `len` bytes (or be null).
|
||||||
|
/// * `response` must be a valid, writable `i64` pointer.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn rpg_dsply_read_len(
|
||||||
|
ptr: *const u8,
|
||||||
|
len: i64,
|
||||||
|
response: *mut i64,
|
||||||
|
) {
|
||||||
|
use std::io::BufRead;
|
||||||
|
|
||||||
|
// Build the prompt string the same way rpg_dsply does.
|
||||||
|
let bytes = if ptr.is_null() || len <= 0 {
|
||||||
|
b"" as &[u8]
|
||||||
|
} else {
|
||||||
|
unsafe { slice::from_raw_parts(ptr, len as usize) }
|
||||||
|
};
|
||||||
|
let trimmed = rtrim_spaces(bytes);
|
||||||
|
let text = String::from_utf8_lossy(trimmed);
|
||||||
|
|
||||||
|
{
|
||||||
|
let stdout = io::stdout();
|
||||||
|
let mut out = stdout.lock();
|
||||||
|
let _ = writeln!(out, "DSPLY {}", text);
|
||||||
|
let _ = out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read one line from stdin and parse it as i64.
|
||||||
|
let stdin = io::stdin();
|
||||||
|
let mut line = String::new();
|
||||||
|
if stdin.lock().read_line(&mut line).is_ok() {
|
||||||
|
let trimmed_line = line.trim();
|
||||||
|
if let Ok(n) = trimmed_line.parse::<i64>() {
|
||||||
|
unsafe { *response = n; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
// rpg_halt — abnormal termination
|
// rpg_halt — abnormal termination
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
DSPLY
|
DSPLY Enter a positive integer (or 0 to exit):
|
||||||
DSPLY Sequence for 8:
|
DSPLY Sequence for 8:
|
||||||
DSPLY 4
|
DSPLY 4
|
||||||
DSPLY 2
|
DSPLY 2
|
||||||
DSPLY 1
|
DSPLY 1
|
||||||
DSPLY Reached 1 in 3 iterations.
|
DSPLY Reached 1 in 3 iterations.
|
||||||
DSPLY
|
DSPLY
|
||||||
DSPLY
|
DSPLY Enter a positive integer (or 0 to exit):
|
||||||
|
|||||||
116
src/codegen.rs
116
src/codegen.rs
@@ -341,13 +341,22 @@ impl<'ctx> Codegen<'ctx> {
|
|||||||
self.module.add_function("rpg_concat", concat_ty, None);
|
self.module.add_function("rpg_concat", concat_ty, None);
|
||||||
|
|
||||||
// void rpg_dsply_read(const char *prompt, i64 *response)
|
// void rpg_dsply_read(const char *prompt, i64 *response)
|
||||||
// Three-operand DSPLY: display prompt and read an i64 from stdin.
|
// Three-operand DSPLY: display null-terminated prompt and read an i64 from stdin.
|
||||||
let i64_ptr = self.context.ptr_type(AddressSpace::default());
|
let i64_ptr = self.context.ptr_type(AddressSpace::default());
|
||||||
let dsply_read_ty = void_t.fn_type(
|
let dsply_read_ty = void_t.fn_type(
|
||||||
&[i8_ptr.into(), i64_ptr.into()],
|
&[i8_ptr.into(), i64_ptr.into()],
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
self.module.add_function("rpg_dsply_read", dsply_read_ty, None);
|
self.module.add_function("rpg_dsply_read", dsply_read_ty, None);
|
||||||
|
|
||||||
|
// void rpg_dsply_read_len(const u8 *ptr, i64 len, i64 *response)
|
||||||
|
// Three-operand DSPLY where the prompt is a length-delimited byte buffer
|
||||||
|
// (Char / VarChar variable) rather than a null-terminated C string.
|
||||||
|
let dsply_read_len_ty = void_t.fn_type(
|
||||||
|
&[i8_ptr.into(), i64_t.into(), i64_ptr.into()],
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
self.module.add_function("rpg_dsply_read_len", dsply_read_len_ty, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Global declarations ─────────────────────────────────────────────────
|
// ── Global declarations ─────────────────────────────────────────────────
|
||||||
@@ -745,8 +754,10 @@ impl<'ctx> Codegen<'ctx> {
|
|||||||
state: &mut FnState<'ctx>,
|
state: &mut FnState<'ctx>,
|
||||||
) -> Result<(), CodegenError> {
|
) -> Result<(), CodegenError> {
|
||||||
match ty {
|
match ty {
|
||||||
TypeSpec::Char(size_expr) => {
|
TypeSpec::Char(size_expr) | TypeSpec::VarChar(size_expr) => {
|
||||||
// Copy a string literal into the char buffer (space-padded).
|
// Copy a string literal into the char/varchar buffer (space-padded to
|
||||||
|
// the declared length so that rpg_dsply and rpg_dsply_read_len get
|
||||||
|
// clean data with no uninitialized bytes past the content).
|
||||||
if let Expression::Literal(Literal::String(s)) = expr {
|
if let Expression::Literal(Literal::String(s)) = expr {
|
||||||
let field_len = const_int_from_expr(size_expr).unwrap_or(s.len() as u64) as usize;
|
let field_len = const_int_from_expr(size_expr).unwrap_or(s.len() as u64) as usize;
|
||||||
// Build a space-padded string of exactly `field_len` bytes.
|
// Build a space-padded string of exactly `field_len` bytes.
|
||||||
@@ -1005,30 +1016,6 @@ impl<'ctx> Codegen<'ctx> {
|
|||||||
// When a response variable is present we display the prompt and then
|
// When a response variable is present we display the prompt and then
|
||||||
// read an integer from stdin into the response variable.
|
// read an integer from stdin into the response variable.
|
||||||
if let Some(resp_name) = &d.response {
|
if let Some(resp_name) = &d.response {
|
||||||
// Coerce the prompt expression to a C string pointer.
|
|
||||||
let prompt_ptr = match &d.expr {
|
|
||||||
Expression::Variable(qname) => {
|
|
||||||
let name = qname.leaf();
|
|
||||||
if let Some((ptr, _)) = self.resolve_var(name, state) {
|
|
||||||
ptr.into()
|
|
||||||
} else {
|
|
||||||
self.intern_string("").into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Expression::Literal(Literal::String(s)) => {
|
|
||||||
self.intern_string(s).into()
|
|
||||||
}
|
|
||||||
other => {
|
|
||||||
if let Ok(BasicValueEnum::PointerValue(ptr)) =
|
|
||||||
self.gen_expression(other, state)
|
|
||||||
{
|
|
||||||
ptr.into()
|
|
||||||
} else {
|
|
||||||
self.intern_string("").into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Resolve the response variable; allocate a fresh i64 slot in the
|
// Resolve the response variable; allocate a fresh i64 slot in the
|
||||||
// function entry block if not already present. Using
|
// function entry block if not already present. Using
|
||||||
// alloca_i64_in_entry ensures the slot is created once regardless
|
// alloca_i64_in_entry ensures the slot is created once regardless
|
||||||
@@ -1055,14 +1042,83 @@ impl<'ctx> Codegen<'ctx> {
|
|||||||
p
|
p
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(read_fn) = self.module.get_function("rpg_dsply_read") {
|
// Choose the right runtime call based on the prompt operand type:
|
||||||
|
//
|
||||||
|
// • Char/VarChar variable → rpg_dsply_read_len(ptr, len, resp)
|
||||||
|
// The storage is a raw byte buffer with no null terminator, so
|
||||||
|
// we pass the pointer and byte length directly.
|
||||||
|
//
|
||||||
|
// • String literal or other expression → rpg_dsply_read(cstr, resp)
|
||||||
|
// intern_string always null-terminates its result.
|
||||||
|
match &d.expr {
|
||||||
|
Expression::Variable(qname) => {
|
||||||
|
let name = qname.leaf();
|
||||||
|
if let Some((ptr, ty)) = self.resolve_var(name, state) {
|
||||||
|
match &ty {
|
||||||
|
TypeSpec::Char(_) | TypeSpec::VarChar(_) => {
|
||||||
|
// Length-delimited path.
|
||||||
|
let len = ty.byte_size().unwrap_or(0);
|
||||||
|
let len_val = self.context.i64_type().const_int(len, false);
|
||||||
|
if let Some(read_fn) = self.module.get_function("rpg_dsply_read_len") {
|
||||||
self.builder
|
self.builder
|
||||||
.build_call(read_fn, &[prompt_ptr, resp_ptr.into()], "dsply_read")
|
.build_call(
|
||||||
|
read_fn,
|
||||||
|
&[ptr.into(), len_val.into(), resp_ptr.into()],
|
||||||
|
"dsply_read_len",
|
||||||
|
)
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
return Ok(());
|
}
|
||||||
|
_ => {
|
||||||
|
// Treat as a null-terminated C string (e.g. a
|
||||||
|
// pointer returned by %Char / rpg_char_i64).
|
||||||
|
if let Some(read_fn) = self.module.get_function("rpg_dsply_read") {
|
||||||
|
self.builder
|
||||||
|
.build_call(read_fn, &[ptr.into(), resp_ptr.into()], "dsply_read")
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Unknown variable — display an empty prompt and still read.
|
||||||
|
let empty = self.intern_string("");
|
||||||
|
if let Some(read_fn) = self.module.get_function("rpg_dsply_read") {
|
||||||
|
self.builder
|
||||||
|
.build_call(read_fn, &[empty.into(), resp_ptr.into()], "dsply_read")
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expression::Literal(Literal::String(s)) => {
|
||||||
|
let s = s.clone();
|
||||||
|
let cstr = self.intern_string(&s);
|
||||||
|
if let Some(read_fn) = self.module.get_function("rpg_dsply_read") {
|
||||||
|
self.builder
|
||||||
|
.build_call(read_fn, &[cstr.into(), resp_ptr.into()], "dsply_read")
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
// Evaluate the expression; if it yields a pointer (e.g. the
|
||||||
|
// result of a %Char() / rpg_concat call) use the cstr path.
|
||||||
|
let other = other.clone();
|
||||||
|
let prompt_ptr = if let Ok(BasicValueEnum::PointerValue(ptr)) =
|
||||||
|
self.gen_expression(&other, state)
|
||||||
|
{
|
||||||
|
ptr
|
||||||
|
} else {
|
||||||
|
self.intern_string("")
|
||||||
|
};
|
||||||
|
if let Some(read_fn) = self.module.get_function("rpg_dsply_read") {
|
||||||
|
self.builder
|
||||||
|
.build_call(read_fn, &[prompt_ptr.into(), resp_ptr.into()], "dsply_read")
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
// ── One-operand form: DSPLY expr ──────────────────────────────────
|
// ── One-operand form: DSPLY expr ──────────────────────────────────
|
||||||
match &d.expr {
|
match &d.expr {
|
||||||
Expression::Variable(qname) => {
|
Expression::Variable(qname) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user