add: more samples and make them work
This commit is contained in:
122
src/codegen.rs
122
src/codegen.rs
@@ -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);
|
||||
|
||||
67
src/lower.rs
67
src/lower.rs
@@ -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 }))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user