Skip to content

Commit 04eb1bd

Browse files
feat: Implement IfExpression handling in JVM backend
Adds the capability to compile IfExpressions in the KayJam language to JVM bytecode. Implemented `visitIfExpression` in: - `TypeKayJamExpressionVisitor.java`: Performs type checking on the condition (must be boolean) and ensures type consistency between the true and false branches if an else clause exists. - `JVMKayJamExpressionVisitor.java`: Generates JVM bytecode for if-else statements using appropriate labels and conditional/unconditional jump instructions (IFEQ, GOTO). A new unit test `JVMIfExpressionTest.java` has been added. This test compiles a simple KayJam class containing an if-expression within a constant initializer and verifies that the corresponding .class file is generated by the JVM backend compiler. This provides foundational support for conditional logic in the language.
1 parent e6083f2 commit 04eb1bd

File tree

3 files changed

+120
-1
lines changed

3 files changed

+120
-1
lines changed

backend/jvm/src/main/java/com/github/kayjamlang/backend/jvm/JVMKayJamExpressionVisitor.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import com.github.kayjamlang.core.expressions.loops.WhileExpression;
1313
import org.objectweb.asm.ClassWriter;
1414
import org.objectweb.asm.FieldVisitor;
15+
import org.objectweb.asm.Label;
1516
import org.objectweb.asm.MethodVisitor;
1617

1718
import java.util.ArrayDeque;
@@ -219,6 +220,33 @@ public Object visitGetExpression(GetExpression expression) {
219220

220221
@Override
221222
public Object visitIfExpression(IfExpression expression) {
223+
MethodVisitor mv = currentMethodVisitor;
224+
Label elseLabel = new Label();
225+
Label endIfLabel = new Label();
226+
227+
// 1. Visit condition
228+
visit(expression.condition); // Leaves boolean on stack
229+
mv.visitJumpInsn(IFEQ, elseLabel); // Jumps if condition is false (0)
230+
popStack(1); // IFEQ consumes the boolean from the stack
231+
232+
// 2. True branch
233+
visit(expression.ifTrue);
234+
if (expression.ifFalse != null) {
235+
mv.visitJumpInsn(GOTO, endIfLabel); // Skip false branch
236+
}
237+
// If expression.ifFalse is null, execution naturally falls through to endIfLabel if condition was true.
238+
239+
// 3. False branch
240+
mv.visitLabel(elseLabel);
241+
if (expression.ifFalse != null) {
242+
visit(expression.ifFalse);
243+
}
244+
// If ifFalse is null, and condition was false, we jump to elseLabel,
245+
// execute nothing here, and then proceed to endIfLabel.
246+
247+
// 4. End-if
248+
mv.visitLabel(endIfLabel);
249+
222250
return null;
223251
}
224252

backend/jvm/src/main/java/com/github/kayjamlang/backend/jvm/TypeKayJamExpressionVisitor.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,22 @@ public JVMType visitGetExpression(GetExpression expression) {
6767

6868
@Override
6969
public JVMType visitIfExpression(IfExpression expression) {
70-
return null;
70+
JVMType conditionType = visit(expression.condition);
71+
if (!(conditionType instanceof BooleanJVMType)) {
72+
throw new RuntimeException("If expression condition must be boolean, got " + conditionType);
73+
}
74+
75+
JVMType trueBranchType = visit(expression.ifTrue);
76+
77+
if (expression.ifFalse != null) {
78+
JVMType falseBranchType = visit(expression.ifFalse);
79+
if (!trueBranchType.equals(falseBranchType)) {
80+
throw new RuntimeException("If expression branches must have the same type, got " + trueBranchType + " and " + falseBranchType);
81+
}
82+
return trueBranchType;
83+
} else {
84+
return trueBranchType;
85+
}
7186
}
7287

7388
@Override
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.github.kayjamlang.backend.jvm;
2+
3+
import com.github.kayjamlang.core.KayJamFile; // May not be directly used, but good for context
4+
import org.junit.Test;
5+
import java.io.File;
6+
import java.io.FileWriter;
7+
import java.nio.file.Files;
8+
import java.nio.file.Path;
9+
import static org.junit.Assert.assertTrue;
10+
11+
public class JVMIfExpressionTest {
12+
13+
@Test
14+
public void testIfElseExpressionCompilation() throws Exception {
15+
Path inputDir = null;
16+
Path outputDir = null;
17+
try {
18+
// a. Create temporary input and output directories
19+
inputDir = Files.createTempDirectory("kayjam_input_");
20+
outputDir = Files.createTempDirectory("kayjam_output_");
21+
22+
// b. Create a KayJam source file string
23+
String fileName = "MyIfTest";
24+
// Assuming KayJam syntax for a class with a constant initialized by an if-expression.
25+
// The constant 'MyVal' will be part of the static initializer (<clinit>) of MyIfTestKJ.class
26+
String kayJamSource = "class " + fileName + " { const MyVal = if (true) { 1 } else { 2 } }";
27+
File sourceFile = new File(inputDir.toFile(), fileName + ".kj");
28+
29+
try (FileWriter writer = new FileWriter(sourceFile)) {
30+
writer.write(kayJamSource);
31+
}
32+
33+
// c. Create JVMBackendOptions
34+
JVMBackendOptions options = new JVMBackendOptions();
35+
options.setInputDir(inputDir.toFile());
36+
options.setOutputDir(outputDir.toFile());
37+
// options.setNamespace(""); // Assuming default empty namespace for now
38+
39+
// d. Compile
40+
JVMBackendCompiler compiler = JVMBackendCompiler.INSTANCE;
41+
compiler.compile(options);
42+
43+
// e. Check if the class file was created
44+
// Based on JVMKayJamExpressionVisitor:
45+
// namespace is normalized. If empty, it's likely just the class name.
46+
// className is normalizeClassName(container.name) + "KJ" for files, or normalizeClassName(classContainer.name) for classes.
47+
// For a class named MyIfTest, this should become MyIfTest.class (if it's a class container direct output)
48+
// or MyIfTestKJ.class if it's treated like a file container.
49+
// The current KayJamFile logic wraps classes within a file, so the file name matters.
50+
// The file is MyIfTest.kj, so it might generate MyIfTestKJ.class in the root of outputDir.
51+
// However, the KayJamSource defines a class "MyIfTest".
52+
// The visitKayJamFile method in JVMKayJamExpressionVisitor processes classes defined within the file.
53+
// It uses `packageName + "/" + normalizeClassName(classContainer.name)`
54+
// Adjust class name based on actual naming convention by compiler
55+
// As per subtask description, checking for fileName + "KJ.class"
56+
File expectedClassFile = new File(outputDir.toFile(), fileName + "KJ.class");
57+
assertTrue("Expected class file was not generated: " + expectedClassFile.getAbsolutePath(), expectedClassFile.exists());
58+
59+
} finally {
60+
// f. Cleanup (optional for worker, but good practice)
61+
if (inputDir != null) {
62+
// Simple cleanup, could be more robust
63+
Files.walk(inputDir)
64+
.sorted(java.util.Comparator.reverseOrder())
65+
.map(Path::toFile)
66+
.forEach(File::delete);
67+
}
68+
if (outputDir != null) {
69+
Files.walk(outputDir)
70+
.sorted(java.util.Comparator.reverseOrder())
71+
.map(Path::toFile)
72+
.forEach(File::delete);
73+
}
74+
}
75+
}
76+
}

0 commit comments

Comments
 (0)