Skip to content

Commit dd16887

Browse files
authored
feat: implement method calls, recursive structs, and pointer comparisons (#272)
This commit introduces several core enhancements to the LLVM backend and the parser, including support for method-style function calls, recursive struct definitions via opaque types, and robust pointer arithmetic/comparisons. Changes: - **Method Calls & Structs**: - Implemented method-style calls (`obj.method()`) by resolving to `struct_name_method_name` functions and automatically passing `self`. - Refactored struct type generation to use **Opaque Structs**, allowing for recursive data structures (e.g., linked lists). - Improved `FieldAccess` to use centralized address generation logic. - Added implicit type coercion for struct literal fields. - **Pointer & Type Logic**: - Added support for binary comparisons between pointers (`ptr == ptr`) and mixed pointer-integer comparisons (`ptr == 0`). - Implemented implicit type coercion for assignments and return statements. - Added integer promotion (z-extend) for values smaller than 32-bit in formatted IO functions. - **Lexer & Parser Enhancements**: - Added safety check to prevent unescaped newlines in string literals. - Updated type parser to skip newlines, improving formatting flexibility for generic types. - Registered `CharLiteral` as a valid expression start. - **Formatting Engine**: - Enhanced the formatting logic to support specifiers within placeholders (e.g., `{x}` for hex, `{c}` for char, `{p}` for pointer). - Added automatic C-string (`%s`) mapping for `i8` pointers. - **Testing & Utils**: - Significantly updated `test56.wave` with a robust TCP socket server implementation demonstrating new syscalls, structs, and methods. - Added a Python test runner helper for verifying server-side responses. - Added `parse_placeholders` to `utils` for advanced format specifier parsing. This update significantly increases the language's expressiveness, enabling more complex system-level programming patterns. Signed-off-by: LunaStev <luna@lunastev.org>
1 parent fd6934e commit dd16887

File tree

15 files changed

+541
-163
lines changed

15 files changed

+541
-163
lines changed

front/lexer/src/lexer/literals.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ use super::Lexer;
22

33
impl<'a> Lexer<'a> {
44
pub(crate) fn string(&mut self) -> String {
5-
if self.peek() == '"' { self.advance(); }
6-
75
let mut string_literal = String::new();
86
while !self.is_at_end() && self.peek() != '"' {
7+
if self.peek() == '\n' {
8+
panic!("Unterminated string (newline in string literal).");
9+
}
10+
911
let c = self.advance();
1012

1113
if c == '\\' {

front/parser/src/parser/types.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ pub fn is_expression_start(token_type: &TokenType) -> bool {
6363
| TokenType::Lbrack
6464
| TokenType::Asm
6565
| TokenType::Deref
66+
| TokenType::CharLiteral(_)
6667
)
6768
}
6869

@@ -218,7 +219,9 @@ pub fn parse_type_from_stream(tokens: &mut Peekable<Iter<Token>>) -> Option<Wave
218219
let type_token = tokens.next()?;
219220

220221
if let TokenType::Identifier(name) = &type_token.token_type {
221-
while matches!(tokens.peek().map(|t| &t.token_type), Some(TokenType::Whitespace)) {
222+
while matches!(tokens.peek().map(|t| &t.token_type),
223+
Some(TokenType::Whitespace | TokenType::Newline)
224+
) {
222225
tokens.next();
223226
}
224227

llvm_temporary/src/llvm_temporary/expression/rvalue/assign.rs

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::llvm_temporary::llvm_codegen::generate_address_ir;
33
use inkwell::types::{AnyTypeEnum, BasicTypeEnum};
44
use inkwell::values::{BasicValue, BasicValueEnum};
55
use parser::ast::{AssignOperator, Expression};
6+
use crate::llvm_temporary::statement::variable::{coerce_basic_value, CoercionMode};
67

78
pub(crate) fn gen_assign_operation<'ctx, 'a>(
89
env: &mut ExprGenEnv<'ctx, 'a>,
@@ -14,6 +15,34 @@ pub(crate) fn gen_assign_operation<'ctx, 'a>(
1415
env.context, env.builder, target, env.variables, env.module, env.struct_types, env.struct_field_indices
1516
);
1617

18+
let element_type = match ptr.get_type().get_element_type() {
19+
AnyTypeEnum::IntType(t) => BasicTypeEnum::IntType(t),
20+
AnyTypeEnum::FloatType(t) => BasicTypeEnum::FloatType(t),
21+
AnyTypeEnum::PointerType(t) => BasicTypeEnum::PointerType(t),
22+
AnyTypeEnum::ArrayType(t) => BasicTypeEnum::ArrayType(t),
23+
AnyTypeEnum::StructType(t) => BasicTypeEnum::StructType(t),
24+
AnyTypeEnum::VectorType(t) => BasicTypeEnum::VectorType(t),
25+
_ => panic!("Unsupported LLVM element type"),
26+
};
27+
28+
if matches!(operator, AssignOperator::Assign) {
29+
let mut rhs = env.gen(value, Some(element_type));
30+
31+
if rhs.get_type() != element_type {
32+
rhs = coerce_basic_value(
33+
env.context,
34+
env.builder,
35+
rhs,
36+
element_type,
37+
"assign_cast",
38+
CoercionMode::Implicit,
39+
);
40+
}
41+
42+
env.builder.build_store(ptr, rhs).unwrap();
43+
return rhs;
44+
}
45+
1746
let current_val = env.builder.build_load(ptr, "load_current").unwrap();
1847

1948
let new_val = env.gen(value, Some(current_val.get_type()));
@@ -55,17 +84,7 @@ pub(crate) fn gen_assign_operation<'ctx, 'a>(
5584
AssignOperator::RemAssign => env.builder.build_float_rem(lhs, rhs, "rem_assign").unwrap().as_basic_value_enum(),
5685
},
5786

58-
_ => panic!("Type mismatch or unsupported type in AssignOperation"),
59-
};
60-
61-
let element_type = match ptr.get_type().get_element_type() {
62-
AnyTypeEnum::IntType(t) => BasicTypeEnum::IntType(t),
63-
AnyTypeEnum::FloatType(t) => BasicTypeEnum::FloatType(t),
64-
AnyTypeEnum::PointerType(t) => BasicTypeEnum::PointerType(t),
65-
AnyTypeEnum::ArrayType(t) => BasicTypeEnum::ArrayType(t),
66-
AnyTypeEnum::StructType(t) => BasicTypeEnum::StructType(t),
67-
AnyTypeEnum::VectorType(t) => BasicTypeEnum::VectorType(t),
68-
_ => panic!("Unsupported LLVM element type"),
87+
_ => panic!("AssignOperation (+=, -=, ...) only supports numeric types"),
6988
};
7089

7190
let result_casted = match (result, element_type) {

llvm_temporary/src/llvm_temporary/expression/rvalue/binary.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,75 @@ pub(crate) fn gen<'ctx, 'a>(
144144
_ => panic!("Unsupported mixed-type operator (float + int)"),
145145
}
146146
}
147+
(BasicValueEnum::PointerValue(lp), BasicValueEnum::PointerValue(rp)) => {
148+
let i64_ty = env.context.i64_type();
149+
let li = env.builder.build_ptr_to_int(lp, i64_ty, "l_ptr2int").unwrap();
150+
let ri = env.builder.build_ptr_to_int(rp, i64_ty, "r_ptr2int").unwrap();
151+
152+
let mut result = match operator {
153+
Operator::Equal => env.builder.build_int_compare(IntPredicate::EQ, li, ri, "ptreq").unwrap(),
154+
Operator::NotEqual => env.builder.build_int_compare(IntPredicate::NE, li, ri, "ptrne").unwrap(),
155+
_ => panic!("Unsupported pointer operator: {:?}", operator),
156+
};
157+
158+
if let Some(inkwell::types::BasicTypeEnum::IntType(target_ty)) = expected_type {
159+
if result.get_type() != target_ty {
160+
result = env.builder.build_int_cast(result, target_ty, "cast_result").unwrap();
161+
}
162+
}
163+
164+
return result.as_basic_value_enum();
165+
}
166+
167+
(BasicValueEnum::PointerValue(lp), BasicValueEnum::IntValue(ri)) => {
168+
let i64_ty = env.context.i64_type();
169+
let li = env.builder.build_ptr_to_int(lp, i64_ty, "l_ptr2int").unwrap();
170+
171+
let ri = if ri.get_type().get_bit_width() == 64 {
172+
ri
173+
} else {
174+
env.builder.build_int_cast(ri, i64_ty, "r_i64").unwrap()
175+
};
176+
177+
let mut result = match operator {
178+
Operator::Equal => env.builder.build_int_compare(IntPredicate::EQ, li, ri, "ptreq0").unwrap(),
179+
Operator::NotEqual => env.builder.build_int_compare(IntPredicate::NE, li, ri, "ptrne0").unwrap(),
180+
_ => panic!("Unsupported ptr/int operator: {:?}", operator),
181+
};
182+
183+
if let Some(inkwell::types::BasicTypeEnum::IntType(target_ty)) = expected_type {
184+
if result.get_type() != target_ty {
185+
result = env.builder.build_int_cast(result, target_ty, "cast_result").unwrap();
186+
}
187+
}
188+
189+
return result.as_basic_value_enum();
190+
}
191+
192+
(BasicValueEnum::IntValue(li), BasicValueEnum::PointerValue(rp)) => {
193+
let i64_ty = env.context.i64_type();
194+
let li = if li.get_type().get_bit_width() == 64 {
195+
li
196+
} else {
197+
env.builder.build_int_cast(li, i64_ty, "l_i64").unwrap()
198+
};
199+
200+
let ri = env.builder.build_ptr_to_int(rp, i64_ty, "r_ptr2int").unwrap();
201+
202+
let mut result = match operator {
203+
Operator::Equal => env.builder.build_int_compare(IntPredicate::EQ, li, ri, "ptreq0").unwrap(),
204+
Operator::NotEqual => env.builder.build_int_compare(IntPredicate::NE, li, ri, "ptrne0").unwrap(),
205+
_ => panic!("Unsupported int/ptr operator: {:?}", operator),
206+
};
207+
208+
if let Some(inkwell::types::BasicTypeEnum::IntType(target_ty)) = expected_type {
209+
if result.get_type() != target_ty {
210+
result = env.builder.build_int_cast(result, target_ty, "cast_result").unwrap();
211+
}
212+
}
213+
214+
return result.as_basic_value_enum();
215+
}
147216

148217
_ => panic!("Type mismatch in binary expression"),
149218
}

llvm_temporary/src/llvm_temporary/expression/rvalue/calls.rs

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,32 @@
1-
use inkwell::types::BasicTypeEnum;
1+
use inkwell::types::{AsTypeRef, BasicTypeEnum};
22
use super::ExprGenEnv;
33
use inkwell::values::{BasicMetadataValueEnum, BasicValue, BasicValueEnum};
44
use parser::ast::{Expression, WaveType};
55
use crate::llvm_temporary::statement::variable::{coerce_basic_value, CoercionMode};
66

7+
fn normalize_struct_name(raw: &str) -> &str {
8+
raw.strip_prefix("struct.").unwrap_or(raw).trim_start_matches('%')
9+
}
10+
11+
fn resolve_struct_key<'ctx>(
12+
st: inkwell::types::StructType<'ctx>,
13+
struct_types: &std::collections::HashMap<String, inkwell::types::StructType<'ctx>>,
14+
) -> String {
15+
if let Some(raw) = st.get_name().and_then(|n| n.to_str().ok()) {
16+
return normalize_struct_name(raw).to_string();
17+
}
18+
19+
let st_ref = st.as_type_ref();
20+
for (name, ty) in struct_types {
21+
if ty.as_type_ref() == st_ref {
22+
return name.clone();
23+
}
24+
}
25+
26+
panic!("LLVM struct type has no name and cannot be matched to struct_types");
27+
}
28+
29+
730
pub(crate) fn gen_method_call<'ctx, 'a>(
831
env: &mut ExprGenEnv<'ctx, 'a>,
932
object: &Expression,
@@ -59,6 +82,67 @@ pub(crate) fn gen_method_call<'ctx, 'a>(
5982
}
6083
}
6184

85+
{
86+
let obj_preview = env.gen(object, None);
87+
let obj_ty = obj_preview.get_type();
88+
89+
let struct_name_opt: Option<String> = match obj_ty {
90+
BasicTypeEnum::StructType(st) => Some(resolve_struct_key(st, env.struct_types)),
91+
BasicTypeEnum::PointerType(p) if p.get_element_type().is_struct_type() => {
92+
Some(resolve_struct_key(p.get_element_type().into_struct_type(), env.struct_types))
93+
}
94+
_ => None,
95+
};
96+
97+
if let Some(struct_name) = struct_name_opt {
98+
let fn_name = format!("{}_{}", struct_name, name);
99+
100+
if let Some(function) = env.module.get_function(&fn_name) {
101+
let fn_type = function.get_type();
102+
let param_types = fn_type.get_param_types();
103+
let expected_self = param_types.get(0).cloned();
104+
105+
let mut obj_val = obj_preview;
106+
if let Some(et) = expected_self {
107+
obj_val = coerce_basic_value(
108+
env.context,
109+
env.builder,
110+
obj_val,
111+
et,
112+
"self_cast",
113+
CoercionMode::Implicit,
114+
);
115+
}
116+
117+
let mut call_args: Vec<BasicMetadataValueEnum> = Vec::new();
118+
call_args.push(obj_val.into());
119+
120+
for (i, arg_expr) in args.iter().enumerate() {
121+
let expected_ty = param_types.get(i + 1).cloned();
122+
let mut arg_val = env.gen(arg_expr, expected_ty);
123+
if let Some(et) = expected_ty {
124+
arg_val = coerce_basic_value(
125+
env.context, env.builder, arg_val, et, &format!("arg{}_cast", i),
126+
CoercionMode::Implicit
127+
);
128+
}
129+
call_args.push(arg_val.into());
130+
}
131+
132+
let call_site = env
133+
.builder
134+
.build_call(function, &call_args, &format!("call_{}", fn_name))
135+
.unwrap();
136+
137+
if function.get_type().get_return_type().is_some() {
138+
return call_site.try_as_basic_value().left().unwrap();
139+
} else {
140+
return env.context.i32_type().const_zero().as_basic_value_enum();
141+
}
142+
}
143+
}
144+
}
145+
62146
// method-style call: fn(self, ...)
63147
let function = env
64148
.module

llvm_temporary/src/llvm_temporary/expression/rvalue/structs.rs

Lines changed: 20 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use super::ExprGenEnv;
22
use inkwell::values::{BasicValue, BasicValueEnum};
33
use parser::ast::{Expression, WaveType};
4+
use crate::llvm_temporary::llvm_codegen::generate_address_ir;
45

56
pub(crate) fn gen_struct_literal<'ctx, 'a>(
67
env: &mut ExprGenEnv<'ctx, 'a>,
@@ -27,7 +28,11 @@ pub(crate) fn gen_struct_literal<'ctx, 'a>(
2728
.get(field_name)
2829
.unwrap_or_else(|| panic!("Field '{}' not found in struct '{}'", field_name, name));
2930

30-
let field_val = env.gen(field_expr, None);
31+
let expected_field_ty = struct_ty
32+
.get_field_type_at_index(*idx)
33+
.unwrap_or_else(|| panic!("No field type at index {} for struct '{}'", idx, name));
34+
35+
let field_val = env.gen(field_expr, Some(expected_field_ty));
3136

3237
let field_ptr = env
3338
.builder
@@ -48,40 +53,24 @@ pub(crate) fn gen_field_access<'ctx, 'a>(
4853
object: &Expression,
4954
field: &str,
5055
) -> BasicValueEnum<'ctx> {
51-
let var_name = match object {
52-
Expression::Variable(name) => name,
53-
other => panic!("FieldAccess on non-variable object not supported yet: {:?}", other),
54-
};
55-
56-
let var_info = env
57-
.variables
58-
.get(var_name)
59-
.unwrap_or_else(|| panic!("Variable '{}' not found for field access", var_name));
60-
61-
let struct_name = match &var_info.ty {
62-
WaveType::Struct(name) => name,
63-
other_ty => panic!(
64-
"Field access on non-struct type {:?} for variable '{}'",
65-
other_ty, var_name
66-
),
56+
let full = Expression::FieldAccess {
57+
object: Box::new(object.clone()),
58+
field: field.to_string(),
6759
};
6860

69-
let field_indices = env
70-
.struct_field_indices
71-
.get(struct_name)
72-
.unwrap_or_else(|| panic!("Field index map for struct '{}' not found", struct_name));
73-
74-
let idx = field_indices
75-
.get(field)
76-
.unwrap_or_else(|| panic!("Field '{}' not found in struct '{}'", field, struct_name));
77-
78-
let field_ptr = env
79-
.builder
80-
.build_struct_gep(var_info.ptr, *idx, &format!("{}.{}", var_name, field))
81-
.unwrap();
61+
let ptr = generate_address_ir(
62+
env.context,
63+
env.builder,
64+
&full,
65+
env.variables,
66+
env.module,
67+
env.struct_types,
68+
env.struct_field_indices,
69+
);
8270

8371
env.builder
84-
.build_load(field_ptr, &format!("load_{}_{}", var_name, field))
72+
.build_load(ptr, &format!("load_field_{}", field))
8573
.unwrap()
8674
.as_basic_value_enum()
8775
}
76+

0 commit comments

Comments
 (0)