add: more samples and make them work

This commit is contained in:
2026-03-12 22:55:14 -07:00
parent 6c4118c489
commit 46935005f7
8 changed files with 525 additions and 90 deletions

View File

@@ -316,6 +316,15 @@ impl<'ctx> Codegen<'ctx> {
// i8* rpg_concat(i8* a, i8* b) — concatenate two C strings
let concat_ty = i8_ptr.fn_type(&[i8_ptr.into(), i8_ptr.into()], false);
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.
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);
}
// ── Global declarations ─────────────────────────────────────────────────
@@ -474,6 +483,49 @@ impl<'ctx> Codegen<'ctx> {
self.builder.build_alloca(arr_ty, name).unwrap()
}
/// Allocate an `i64` slot in the **entry block** of `func`, regardless of
/// where the builder is currently positioned.
///
/// LLVM's `mem2reg` pass (and general best-practice) requires that all
/// `alloca` instructions live in the function entry block. When code is
/// generated inside a loop or branch the builder's insertion point is not
/// the entry block; calling `build_alloca` there produces an alloca that is
/// re-executed on every iteration, creating a new stack slot each time and
/// losing any value stored in a previous iteration.
///
/// This helper saves the current insertion point, moves to the first
/// instruction of the entry block (so the alloca is prepended before any
/// existing code), emits the alloca, then restores the original position.
fn alloca_i64_in_entry(
&self,
func: inkwell::values::FunctionValue<'ctx>,
name: &str,
) -> PointerValue<'ctx> {
let i64_t = self.context.i64_type();
let entry_bb = func.get_first_basic_block().expect("function has no entry block");
// Remember where we are now.
let saved_bb = self.builder.get_insert_block();
// Position at the very start of the entry block so the alloca is
// placed before any other instructions (branches, stores, etc.).
match entry_bb.get_first_instruction() {
Some(first) => self.builder.position_before(&first),
None => self.builder.position_at_end(entry_bb),
}
let ptr = self.builder.build_alloca(i64_t, name).unwrap();
// Restore the builder's original position.
// We always generate code at the end of the current block, so
// position_at_end on the saved block is sufficient.
if let Some(bb) = saved_bb {
self.builder.position_at_end(bb);
}
ptr
}
/// Allocate storage for an array of `n` elements of type `ty`.
fn alloca_for_type_dim(&self, ty: &TypeSpec, name: &str, n: u64) -> PointerValue<'ctx> {
let elem_size = ty.byte_size().unwrap_or(8) as u32;
@@ -777,6 +829,69 @@ impl<'ctx> Codegen<'ctx> {
let dsply = self.module.get_function("rpg_dsply")
.ok_or_else(|| CodegenError::new("rpg_dsply not declared"))?;
// ── Three-operand form: DSPLY expr msgq response ──────────────────
// 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
// of how many times the DSPLY statement is executed (e.g. inside a
// loop), so the stored value persists across iterations.
let resp_ptr: inkwell::values::PointerValue =
if let Some((ptr, TypeSpec::Int(n))) = state.locals.get(resp_name) {
if matches!(n.as_ref(), Expression::Literal(Literal::Integer(20))) {
*ptr
} else {
let p = self.alloca_i64_in_entry(state.function, resp_name);
state.locals.insert(
resp_name.clone(),
(p, TypeSpec::Int(Box::new(Expression::Literal(Literal::Integer(20))))),
);
p
}
} else {
let p = self.alloca_i64_in_entry(state.function, resp_name);
state.locals.insert(
resp_name.clone(),
(p, TypeSpec::Int(Box::new(Expression::Literal(Literal::Integer(20))))),
);
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();
}
return Ok(());
}
// ── One-operand form: DSPLY expr ──────────────────────────────────
match &d.expr {
Expression::Variable(qname) => {
// Look up the variable, then pass ptr + len.
@@ -1029,11 +1144,12 @@ impl<'ctx> Codegen<'ctx> {
*ptr
} else {
// Declared as a narrower int (e.g. INT(10) = 4 bytes).
// Allocate a fresh 8-byte slot; locals will be updated below.
self.builder.build_alloca(i64_t, &f.var).unwrap()
// Allocate a fresh 8-byte slot in the entry block so it is
// not re-created on every loop iteration.
self.alloca_i64_in_entry(func, &f.var)
}
}
_ => self.builder.build_alloca(i64_t, &f.var).unwrap(),
_ => self.alloca_i64_in_entry(func, &f.var),
};
let start = self.gen_expression(&f.start, state)?;
let start_i = self.coerce_to_i64(start);

View File

@@ -1264,8 +1264,12 @@ impl Parser {
self.advance();
self.expect(&Token::LParen)?;
let digits = self.parse_expression()?;
self.expect(&Token::Colon)?;
let decimals = self.parse_expression()?;
// Decimal positions are optional — `Packed(10)` means `Packed(10:0)`.
let decimals = if self.eat(&Token::Colon) {
self.parse_expression()?
} else {
Expression::Literal(Literal::Integer(0))
};
self.expect(&Token::RParen)?;
Ok(TypeSpec::Packed(Box::new(digits), Box::new(decimals)))
}
@@ -1273,8 +1277,12 @@ impl Parser {
self.advance();
self.expect(&Token::LParen)?;
let digits = self.parse_expression()?;
self.expect(&Token::Colon)?;
let decimals = self.parse_expression()?;
// Decimal positions are optional — `Zoned(10)` means `Zoned(10:0)`.
let decimals = if self.eat(&Token::Colon) {
self.parse_expression()?
} else {
Expression::Literal(Literal::Integer(0))
};
self.expect(&Token::RParen)?;
Ok(TypeSpec::Zoned(Box::new(digits), Box::new(decimals)))
}
@@ -1282,8 +1290,12 @@ impl Parser {
self.advance();
self.expect(&Token::LParen)?;
let digits = self.parse_expression()?;
self.expect(&Token::Colon)?;
let decimals = self.parse_expression()?;
// Decimal positions are optional — `Bindec(10)` means `Bindec(10:0)`.
let decimals = if self.eat(&Token::Colon) {
self.parse_expression()?
} else {
Expression::Literal(Literal::Integer(0))
};
self.expect(&Token::RParen)?;
Ok(TypeSpec::Bindec(Box::new(digits), Box::new(decimals)))
}
@@ -1627,28 +1639,21 @@ impl Parser {
fn parse_dsply(&mut self) -> Result<Statement, LowerError> {
self.advance(); // KwDsply
// Two forms:
// Three forms:
// DSPLY expr;
// DSPLY (expr : msgq : response);
// DSPLY (expr : msgq : response); ← parenthesised colon-separated
// DSPLY expr msgq response; ← space-separated (no parens)
if self.peek() == &Token::LParen {
// peek ahead — if the next token after '(' looks like an expression
// followed by ':' it's the three-arg form
self.advance(); // (
let expr = self.parse_expression()?;
let mut msg_q = None;
let mut response = None;
if self.eat(&Token::Colon) {
if let Token::Identifier(s) = self.peek().clone() {
self.advance();
msg_q = Some(s);
} else {
self.eat(&Token::Colon);
}
// Accept any name-like token for msgq / response, including
// tokens that collide with keywords (e.g. a variable `n`).
msg_q = self.try_parse_ident_or_name().map(|s| s.to_lowercase());
if self.eat(&Token::Colon) {
if let Token::Identifier(s) = self.peek().clone() {
self.advance();
response = Some(s);
}
response = self.try_parse_ident_or_name().map(|s| s.to_lowercase());
}
}
self.eat(&Token::RParen);
@@ -1656,8 +1661,28 @@ impl Parser {
Ok(Statement::Dsply(DsplyStmt { expr, msg_q, response }))
} else {
let expr = self.parse_expression()?;
// Space-separated msgq and response operands (no parentheses):
// DSPLY prompt ' ' response_var;
// DSPLY prompt msgq response_var;
// After the expression, a string literal or another identifier
// signals the optional msgq operand, followed by the response var.
let mut msg_q = None;
let mut response = None;
match self.peek().clone() {
Token::StringLit(s) => {
self.advance();
msg_q = Some(s);
// Optional response variable follows the msgq.
response = self.try_parse_ident_or_name().map(|s| s.to_lowercase());
}
Token::Identifier(_) => {
msg_q = self.try_parse_ident_or_name().map(|s| s.to_lowercase());
response = self.try_parse_ident_or_name().map(|s| s.to_lowercase());
}
_ => {}
}
self.eat_semicolon();
Ok(Statement::Dsply(DsplyStmt { expr, msg_q: None, response: None }))
Ok(Statement::Dsply(DsplyStmt { expr, msg_q, response }))
}
}