fix: VarChar handling raw string, VarChar Inz silent drop

This commit is contained in:
2026-03-12 23:19:46 -07:00
parent 8e36afbf67
commit 832dae44fb
3 changed files with 146 additions and 35 deletions

View File

@@ -341,13 +341,22 @@ impl<'ctx> Codegen<'ctx> {
self.module.add_function("rpg_concat", concat_ty, None);
// 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 dsply_read_ty = void_t.fn_type(
&[i8_ptr.into(), i64_ptr.into()],
false,
);
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 ─────────────────────────────────────────────────
@@ -745,8 +754,10 @@ impl<'ctx> Codegen<'ctx> {
state: &mut FnState<'ctx>,
) -> Result<(), CodegenError> {
match ty {
TypeSpec::Char(size_expr) => {
// Copy a string literal into the char buffer (space-padded).
TypeSpec::Char(size_expr) | TypeSpec::VarChar(size_expr) => {
// 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 {
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.
@@ -1005,30 +1016,6 @@ impl<'ctx> Codegen<'ctx> {
// When a response variable is present we display the prompt and then
// read an integer from stdin into the response variable.
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
// function entry block if not already present. Using
// alloca_i64_in_entry ensures the slot is created once regardless
@@ -1055,14 +1042,83 @@ impl<'ctx> Codegen<'ctx> {
p
};
if let Some(read_fn) = self.module.get_function("rpg_dsply_read") {
self.builder
.build_call(read_fn, &[prompt_ptr, resp_ptr.into()], "dsply_read")
.ok();
// 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
.build_call(
read_fn,
&[ptr.into(), len_val.into(), resp_ptr.into()],
"dsply_read_len",
)
.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 ──────────────────────────────────
match &d.expr {
Expression::Variable(qname) => {