Skip to content

Commit cce40f4

Browse files
authored
feat: implement input() statement and lvalue generation (#259)
Add support for reading user input using `scanf` and reorganize expression handling to support L-value generation. Changes: - AST/Parser: - Add `StatementNode::Input` variant. - Implement `parse_input` to handle `input("fmt", var)` syntax. - Enforce `input()` format string validation (placeholder count matches args). - Expression Codegen Refactor: - Split `expression.rs` into `mod.rs`, `rvalue.rs`, and `lvalue.rs`. - Move existing R-value generation to `rvalue.rs`. - Implement `generate_lvalue_ir` in `lvalue.rs` to support address generation for: - Variables, Dereferences, AddressOf (`&`). - Array Indexing (`arr[i]`), Struct Field Access (`obj.field`). - Handles nested expressions and ensures type correctness. - LLVM Codegen: - Implement `wave_format_to_scanf` to convert Wave format strings to C-style `scanf` formats. - Generate IR for `input()`: - Calls `scanf` with generated format string and variable addresses. - Validates `scanf` return value (matches expected argument count). - Exits program with code 1 on input failure. - Runner: - Update `run_wave_file` to use `Stdio::inherit()` for stdin/stdout/stderr. - Allows interactive input during execution. - Tests: - Update `test74.wave` to demonstrate `input()` usage. This enables interactive console applications in Wave by providing standard input capabilities. Signed-off-by: LunaStev <luna@lunastev.org>
1 parent c7b21d1 commit cce40f4

File tree

10 files changed

+496
-11
lines changed

10 files changed

+496
-11
lines changed

front/parser/src/parser/ast.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,10 @@ pub enum StatementNode {
198198
format: String,
199199
args: Vec<Expression>,
200200
},
201+
Input {
202+
format: String,
203+
args: Vec<Expression>,
204+
},
201205
Variable(String),
202206
If {
203207
condition: Expression,

front/parser/src/parser/parser.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,19 @@ pub fn extract_body(tokens: &mut Peekable<Iter<Token>>) -> Option<Vec<ASTNode>>
281281
}
282282
body.push(node);
283283
}
284+
TokenType::Input => {
285+
tokens.next(); // consume 'input'
286+
let node = parse_input(tokens)?;
287+
// Added semicolon handling
288+
if let Some(Token {
289+
token_type: TokenType::SemiColon,
290+
..
291+
}) = tokens.peek()
292+
{
293+
tokens.next();
294+
}
295+
body.push(node);
296+
}
284297
TokenType::If => {
285298
tokens.next();
286299
body.push(parse_if(tokens)?);
@@ -956,6 +969,71 @@ fn parse_print(tokens: &mut Peekable<Iter<Token>>) -> Option<ASTNode> {
956969
}))
957970
}
958971

972+
fn parse_input(tokens: &mut Peekable<Iter<Token>>) -> Option<ASTNode> {
973+
if tokens.peek()?.token_type != TokenType::Lparen {
974+
println!("Error: Expected '(' after 'println'");
975+
return None;
976+
}
977+
tokens.next(); // Consume '('
978+
979+
let content = if let Some(Token {
980+
token_type: TokenType::String(content),
981+
..
982+
}) = tokens.next()
983+
{
984+
content.clone() // Need clone() because it is String
985+
} else {
986+
println!("Error: Expected string literal in 'input'");
987+
return None;
988+
};
989+
990+
let placeholder_count = Regex::new(r"\{[^}]*\}")
991+
.unwrap()
992+
.find_iter(&content)
993+
.count();
994+
995+
let mut args = Vec::new();
996+
while let Some(Token {
997+
token_type: TokenType::Comma,
998+
..
999+
}) = tokens.peek()
1000+
{
1001+
tokens.next(); // Consume ','
1002+
if let Some(expr) = parse_expression(tokens) {
1003+
args.push(expr);
1004+
} else {
1005+
println!("Error: Failed to parse expression in 'println'");
1006+
return None;
1007+
}
1008+
}
1009+
1010+
if tokens.peek()?.token_type != TokenType::Rparen {
1011+
println!("Error: Expected closing ')'");
1012+
return None;
1013+
}
1014+
tokens.next(); // Consume ')'
1015+
1016+
if tokens.peek().map(|t| &t.token_type) != Some(&TokenType::SemiColon) {
1017+
println!("Expected ';' after expression");
1018+
return None;
1019+
}
1020+
tokens.next();
1021+
1022+
if placeholder_count != args.len() {
1023+
println!(
1024+
"Error: Expected {} arguments, found {}",
1025+
placeholder_count,
1026+
args.len()
1027+
);
1028+
return None;
1029+
}
1030+
1031+
Some(ASTNode::Statement(StatementNode::Input {
1032+
format: content,
1033+
args,
1034+
}))
1035+
}
1036+
9591037
fn skip_whitespace(tokens: &mut Peekable<Iter<Token>>) {
9601038
while let Some(token) = tokens.peek() {
9611039
if token.token_type == TokenType::Whitespace {
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
use inkwell::{
2+
builder::Builder,
3+
context::Context,
4+
module::Module,
5+
types::StructType,
6+
values::{BasicValueEnum, PointerValue},
7+
AddressSpace,
8+
};
9+
use std::collections::HashMap;
10+
use parser::ast::Expression;
11+
use crate::llvm_temporary::expression::rvalue::generate_expression_ir;
12+
use crate::llvm_temporary::llvm_codegen::VariableInfo;
13+
14+
pub fn generate_lvalue_ir<'ctx>(
15+
context: &'ctx Context,
16+
builder: &'ctx Builder<'ctx>,
17+
expr: &Expression,
18+
variables: &mut HashMap<String, VariableInfo<'ctx>>,
19+
module: &'ctx Module<'ctx>,
20+
global_consts: &HashMap<String, BasicValueEnum<'ctx>>,
21+
struct_types: &HashMap<String, StructType<'ctx>>,
22+
struct_field_indices: &HashMap<String, HashMap<String, u32>>,
23+
) -> PointerValue<'ctx> {
24+
match expr {
25+
Expression::Variable(name) => {
26+
if global_consts.contains_key(name) {
27+
panic!("Cannot use constant '{}' as lvalue", name);
28+
}
29+
30+
let info = variables
31+
.get(name)
32+
.unwrap_or_else(|| panic!("Undefined variable '{}'", name));
33+
34+
info.ptr
35+
}
36+
37+
Expression::Grouped(inner) => generate_lvalue_ir(
38+
context,
39+
builder,
40+
inner,
41+
variables,
42+
module,
43+
global_consts,
44+
struct_types,
45+
struct_field_indices,
46+
),
47+
48+
Expression::Deref(inner) => {
49+
let v = generate_expression_ir(
50+
context,
51+
builder,
52+
inner,
53+
variables,
54+
module,
55+
None,
56+
global_consts,
57+
struct_types,
58+
struct_field_indices,
59+
);
60+
61+
match v {
62+
BasicValueEnum::PointerValue(p) => p,
63+
_ => panic!("Deref target is not a pointer: {:?}", inner),
64+
}
65+
}
66+
67+
Expression::AddressOf(inner) => generate_lvalue_ir(
68+
context,
69+
builder,
70+
inner,
71+
variables,
72+
module,
73+
global_consts,
74+
struct_types,
75+
struct_field_indices,
76+
),
77+
78+
Expression::IndexAccess { target, index } => {
79+
let idx_val = generate_expression_ir(
80+
context,
81+
builder,
82+
index,
83+
variables,
84+
module,
85+
None,
86+
global_consts,
87+
struct_types,
88+
struct_field_indices,
89+
);
90+
91+
let mut idx = match idx_val {
92+
BasicValueEnum::IntValue(iv) => iv,
93+
_ => panic!("Index is not an integer: {:?}", index),
94+
};
95+
96+
if idx.get_type().get_bit_width() != 32 {
97+
idx = builder
98+
.build_int_cast(idx, context.i32_type(), "idx_i32")
99+
.unwrap();
100+
}
101+
102+
let base_ptr: PointerValue<'ctx> = match &**target {
103+
Expression::Variable(_)
104+
| Expression::Deref(_)
105+
| Expression::AddressOf(_)
106+
| Expression::IndexAccess { .. }
107+
| Expression::FieldAccess { .. }
108+
| Expression::Grouped(_) => {
109+
let lv = generate_lvalue_ir(
110+
context,
111+
builder,
112+
target,
113+
variables,
114+
module,
115+
global_consts,
116+
struct_types,
117+
struct_field_indices,
118+
);
119+
120+
let elem_ty = lv.get_type().get_element_type();
121+
if elem_ty.is_pointer_type() {
122+
let loaded = builder.build_load(lv, "load_index_base").unwrap();
123+
match loaded {
124+
BasicValueEnum::PointerValue(p) => p,
125+
_ => panic!("Index base is not a pointer"),
126+
}
127+
} else {
128+
lv
129+
}
130+
}
131+
_ => {
132+
let v = generate_expression_ir(
133+
context,
134+
builder,
135+
target,
136+
variables,
137+
module,
138+
None,
139+
global_consts,
140+
struct_types,
141+
struct_field_indices,
142+
);
143+
match v {
144+
BasicValueEnum::PointerValue(p) => p,
145+
_ => panic!("Index target is not a pointer/array: {:?}", target),
146+
}
147+
}
148+
};
149+
150+
let zero = context.i32_type().const_zero();
151+
let base_elem_ty = base_ptr.get_type().get_element_type();
152+
153+
if base_elem_ty.is_array_type() {
154+
unsafe { builder.build_gep(base_ptr, &[zero, idx], "array_elem_ptr").unwrap() }
155+
} else {
156+
unsafe { builder.build_gep(base_ptr, &[idx], "ptr_elem_ptr").unwrap() }
157+
}
158+
}
159+
160+
Expression::FieldAccess { object, field } => {
161+
let mut obj_ptr = generate_lvalue_ir(
162+
context,
163+
builder,
164+
object,
165+
variables,
166+
module,
167+
global_consts,
168+
struct_types,
169+
struct_field_indices,
170+
);
171+
172+
let obj_elem_ty = obj_ptr.get_type().get_element_type();
173+
if obj_elem_ty.is_pointer_type() {
174+
let loaded = builder.build_load(obj_ptr, "load_struct_ptr").unwrap();
175+
obj_ptr = match loaded {
176+
BasicValueEnum::PointerValue(p) => p,
177+
_ => panic!("FieldAccess base is not a pointer"),
178+
};
179+
}
180+
181+
let struct_ty = obj_ptr
182+
.get_type()
183+
.get_element_type()
184+
.into_struct_type();
185+
186+
let raw_name = struct_ty
187+
.get_name()
188+
.and_then(|c| c.to_str().ok())
189+
.unwrap_or_else(|| panic!("Unnamed struct type; cannot resolve field '{}'", field));
190+
191+
let struct_name = raw_name.strip_prefix("struct.").unwrap_or(raw_name);
192+
193+
let field_index = struct_field_indices
194+
.get(struct_name)
195+
.and_then(|m| m.get(field))
196+
.copied()
197+
.unwrap_or_else(|| panic!("Unknown field '{}.{}'", struct_name, field));
198+
199+
builder
200+
.build_struct_gep(obj_ptr, field_index, "field_ptr")
201+
.unwrap()
202+
}
203+
204+
_ => {
205+
panic!("Expression is not an lvalue (not assignable): {:?}", expr);
206+
}
207+
}
208+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod lvalue;
2+
pub mod rvalue;
File renamed without changes.

llvm_temporary/src/llvm_temporary/llvm_codegen.rs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use inkwell::{AddressSpace, OptimizationLevel};
55
use parser::ast::{ASTNode, Expression, FunctionNode, Literal, Mutability, VariableNode, WaveType};
66

77
use crate::llvm_temporary::statement::generate_statement_ir;
8-
use inkwell::types::{BasicMetadataTypeEnum, BasicType, BasicTypeEnum};
8+
use inkwell::types::{AnyTypeEnum, BasicMetadataTypeEnum, BasicType, BasicTypeEnum};
99
use lexer::token::TokenType;
1010
use std::collections::HashMap;
1111
use std::hash::Hash;
@@ -239,6 +239,63 @@ pub fn wave_format_to_c(format: &str, arg_types: &[BasicTypeEnum]) -> String {
239239
result
240240
}
241241

242+
pub fn wave_format_to_scanf(format: &str, arg_types: &[AnyTypeEnum]) -> String {
243+
let mut result = String::new();
244+
let mut chars = format.chars().peekable();
245+
let mut arg_index = 0;
246+
247+
while let Some(c) = chars.next() {
248+
if c == '{' {
249+
if let Some('}') = chars.peek() {
250+
chars.next(); // consume '}'
251+
252+
let ty = arg_types
253+
.get(arg_index)
254+
.unwrap_or_else(|| panic!("Missing argument for format"));
255+
256+
let fmt = match ty {
257+
AnyTypeEnum::IntType(_) => "%d",
258+
AnyTypeEnum::FloatType(_) => "%f",
259+
260+
AnyTypeEnum::PointerType(ptr_ty) => {
261+
let elem = ptr_ty.get_element_type();
262+
match elem {
263+
AnyTypeEnum::IntType(it)
264+
if it.get_bit_width() == 8 =>
265+
{
266+
"%s" // char*
267+
}
268+
_ => {
269+
panic!("Unsupported pointer type for input");
270+
}
271+
}
272+
}
273+
274+
AnyTypeEnum::StructType(_) => {
275+
panic!("Cannot input into struct directly");
276+
}
277+
278+
AnyTypeEnum::ArrayType(_) => {
279+
panic!("Cannot input into array directly");
280+
}
281+
282+
_ => {
283+
panic!("Unsupported type for input");
284+
}
285+
};
286+
287+
result.push_str(fmt);
288+
arg_index += 1;
289+
continue;
290+
}
291+
}
292+
293+
result.push(c);
294+
}
295+
296+
result
297+
}
298+
242299
pub fn wave_type_to_llvm_type<'ctx>(
243300
context: &'ctx Context,
244301
wave_type: &WaveType,

llvm_temporary/src/llvm_temporary/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
clippy::new_without_default
1818
)]
1919

20-
mod expression;
20+
pub mod expression;
2121
pub mod llvm_backend;
2222
pub mod llvm_codegen;
2323
mod statement;

0 commit comments

Comments
 (0)