Skip to content

Commit 0e4d433

Browse files
authored
Merge pull request #189 from fglock/feature/jsr223-compilable-interface
Add JSR 223 Compilable interface and unified benchmark
2 parents a942a23 + e5223e6 commit 0e4d433

File tree

12 files changed

+800
-78
lines changed

12 files changed

+800
-78
lines changed

docs/about/roadmap.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ The following areas are currently under active development to enhance the functi
6969
- Optimization: faster type resolution in Perl scalars.
7070
- Optimization: `make` now runs tests in parallel.
7171
- Optimization: A workaround is implemented to Java 64k bytes segment limit.
72+
- New command line option: `--interpreter` to run PerlOnJava as an interpreter instead of JVM compiler.
73+
- `./jperl --interpreter --disassemble -e 'print "Hello, World!\n"'`
7274
- Planned release date: 2026-02-10.
7375

7476
- Work in Progress

src/main/java/org/perlonjava/ArgumentParser.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,10 @@ private static int processLongSwitches(String[] args, CompilerOptions parsedArgs
918918
validateExclusiveOptions(parsedArgs, "disassemble");
919919
parsedArgs.disassembleEnabled = true;
920920
break;
921+
case "--interpreter":
922+
// Use bytecode interpreter instead of JVM compiler
923+
parsedArgs.useInterpreter = true;
924+
break;
921925
case "--help":
922926
// Print help message and exit
923927
printHelp();
@@ -1066,6 +1070,7 @@ private static void printHelp() {
10661070
System.out.println(" --tokenize tokenize the input code");
10671071
System.out.println(" --parse parse the input code");
10681072
System.out.println(" --disassemble disassemble the generated code");
1073+
System.out.println(" --interpreter use bytecode interpreter instead of JVM compiler");
10691074
System.out.println(" -h, --help displays this help message");
10701075
System.out.println();
10711076
// System.out.println("Unicode/encoding flags for -C:");

src/main/java/org/perlonjava/CompilerOptions.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
* Fields:
1919
* - debugEnabled: Enables debug mode, providing detailed logging during compilation.
2020
* - disassembleEnabled: If true, the compiler will disassemble the generated bytecode.
21+
* - useInterpreter: If true, use the bytecode interpreter instead of JVM compiler.
2122
* - tokenizeOnly: If true, the compiler will only tokenize the input and stop.
2223
* - parseOnly: If true, the compiler will only parse the input and stop.
2324
* - compileOnly: If true, the compiler will compile the input but won't execute it.
@@ -34,6 +35,7 @@
3435
public class CompilerOptions implements Cloneable {
3536
public boolean debugEnabled = false;
3637
public boolean disassembleEnabled = false;
38+
public boolean useInterpreter = false;
3739
public boolean tokenizeOnly = false;
3840
public boolean parseOnly = false;
3941
public boolean compileOnly = false;
@@ -94,6 +96,7 @@ public String toString() {
9496
return "CompilerOptions{\n" +
9597
" debugEnabled=" + debugEnabled + ",\n" +
9698
" disassembleEnabled=" + disassembleEnabled + ",\n" +
99+
" useInterpreter=" + useInterpreter + ",\n" +
97100
" tokenizeOnly=" + tokenizeOnly + ",\n" +
98101
" parseOnly=" + parseOnly + ",\n" +
99102
" compileOnly=" + compileOnly + ",\n" +

src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,35 @@ public void visit(IdentifierNode node) {
302302

303303
@Override
304304
public void visit(BinaryOperatorNode node) {
305+
// Handle print/say early (special handling for filehandle)
306+
if (node.operator.equals("print") || node.operator.equals("say")) {
307+
// print/say FILEHANDLE LIST
308+
// left = filehandle reference (\*STDERR)
309+
// right = list to print
310+
311+
// Compile the filehandle (left operand)
312+
node.left.accept(this);
313+
int filehandleReg = lastResultReg;
314+
315+
// Compile the content (right operand)
316+
node.right.accept(this);
317+
int contentReg = lastResultReg;
318+
319+
// Emit PRINT or SAY with both registers
320+
emit(node.operator.equals("say") ? Opcodes.SAY : Opcodes.PRINT);
321+
emit(contentReg);
322+
emit(filehandleReg);
323+
324+
// print/say return 1 on success
325+
int rd = allocateRegister();
326+
emit(Opcodes.LOAD_INT);
327+
emit(rd);
328+
emitInt(1);
329+
330+
lastResultReg = rd;
331+
return;
332+
}
333+
305334
// Handle assignment separately (doesn't follow standard left-right-op pattern)
306335
if (node.operator.equals("=")) {
307336
// Special case: my $x = value
@@ -522,6 +551,16 @@ public void visit(BinaryOperatorNode node) {
522551
// Note: CALL_SUB may return RuntimeControlFlowList
523552
// The interpreter will handle control flow propagation
524553
}
554+
case "join" -> {
555+
// String join: rd = join(separator, list)
556+
// left (rs1) = separator (empty string for interpolation)
557+
// right (rs2) = list of elements
558+
559+
emit(Opcodes.JOIN);
560+
emit(rd);
561+
emit(rs1);
562+
emit(rs2);
563+
}
525564
default -> throw new RuntimeException("Unsupported operator: " + node.operator);
526565
}
527566

@@ -593,6 +632,50 @@ public void visit(OperatorNode node) {
593632
} else if (op.equals("%")) {
594633
// Hash variable dereference: %x
595634
throw new RuntimeException("Hash variables not yet supported");
635+
} else if (op.equals("*")) {
636+
// Glob variable dereference: *x
637+
if (node.operand instanceof IdentifierNode) {
638+
IdentifierNode idNode = (IdentifierNode) node.operand;
639+
String varName = idNode.name;
640+
641+
// Add package prefix if not present
642+
if (!varName.contains("::")) {
643+
varName = "main::" + varName;
644+
}
645+
646+
// Allocate register for glob
647+
int rd = allocateRegister();
648+
int nameIdx = addToStringPool(varName);
649+
650+
// Emit SLOW_OP with SLOWOP_LOAD_GLOB
651+
emitWithToken(Opcodes.SLOW_OP, node.getIndex());
652+
emit(Opcodes.SLOWOP_LOAD_GLOB);
653+
emit(rd);
654+
emit(nameIdx);
655+
656+
lastResultReg = rd;
657+
} else {
658+
throw new RuntimeException("Unsupported * operand: " + node.operand.getClass().getSimpleName());
659+
}
660+
} else if (op.equals("\\")) {
661+
// Reference operator: \$x, \@x, \%x, \*x, etc.
662+
if (node.operand != null) {
663+
// Evaluate the operand
664+
node.operand.accept(this);
665+
int valueReg = lastResultReg;
666+
667+
// Allocate register for reference
668+
int rd = allocateRegister();
669+
670+
// Emit CREATE_REF
671+
emit(Opcodes.CREATE_REF);
672+
emit(rd);
673+
emit(valueReg);
674+
675+
lastResultReg = rd;
676+
} else {
677+
throw new RuntimeException("Reference operator requires operand");
678+
}
596679
} else if (op.equals("say") || op.equals("print")) {
597680
// say/print $x
598681
if (node.operand != null) {
@@ -731,6 +814,39 @@ public void visit(OperatorNode node) {
731814
emit(undefReg);
732815
lastResultReg = undefReg;
733816
}
817+
} else if (op.equals("select")) {
818+
// select FILEHANDLE or select()
819+
// SELECT is a fast opcode (used in every print statement)
820+
// Format: [SELECT] [rd] [rs_list]
821+
// Effect: rd = IOOperator.select(registers[rs_list], SCALAR)
822+
823+
int rd = allocateRegister();
824+
825+
if (node.operand != null && node.operand instanceof ListNode) {
826+
// select FILEHANDLE or select() with arguments
827+
// Compile the operand (ListNode containing filehandle ref)
828+
node.operand.accept(this);
829+
int listReg = lastResultReg;
830+
831+
// Emit SELECT opcode
832+
emitWithToken(Opcodes.SELECT, node.getIndex());
833+
emit(rd);
834+
emit(listReg);
835+
} else {
836+
// select() with no arguments - returns current filehandle
837+
// Create empty list
838+
emit(Opcodes.CREATE_LIST);
839+
int listReg = allocateRegister();
840+
emit(listReg);
841+
emit(0); // count = 0
842+
843+
// Emit SELECT opcode
844+
emitWithToken(Opcodes.SELECT, node.getIndex());
845+
emit(rd);
846+
emit(listReg);
847+
}
848+
849+
lastResultReg = rd;
734850
} else {
735851
throw new UnsupportedOperationException("Unsupported operator: " + op);
736852
}

src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java

Lines changed: 108 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -559,18 +559,52 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
559559
// =================================================================
560560

561561
case Opcodes.PRINT: {
562-
// Print to STDOUT
563-
int rs = bytecode[pc++] & 0xFF;
564-
RuntimeScalar val = (RuntimeScalar) registers[rs];
565-
System.out.print(val.toString());
562+
// Print to filehandle
563+
// Format: [PRINT] [rs_content] [rs_filehandle]
564+
int contentReg = bytecode[pc++] & 0xFF;
565+
int filehandleReg = bytecode[pc++] & 0xFF;
566+
567+
Object val = registers[contentReg];
568+
RuntimeScalar fh = (RuntimeScalar) registers[filehandleReg];
569+
570+
RuntimeList list;
571+
if (val instanceof RuntimeList) {
572+
list = (RuntimeList) val;
573+
} else if (val instanceof RuntimeScalar) {
574+
// Convert scalar to single-element list
575+
list = new RuntimeList();
576+
list.add((RuntimeScalar) val);
577+
} else {
578+
list = new RuntimeList();
579+
}
580+
581+
// Call IOOperator.print()
582+
org.perlonjava.operators.IOOperator.print(list, fh);
566583
break;
567584
}
568585

569586
case Opcodes.SAY: {
570-
// Say to STDOUT (print with newline)
571-
int rs = bytecode[pc++] & 0xFF;
572-
RuntimeScalar val = (RuntimeScalar) registers[rs];
573-
System.out.println(val.toString());
587+
// Say to filehandle
588+
// Format: [SAY] [rs_content] [rs_filehandle]
589+
int contentReg = bytecode[pc++] & 0xFF;
590+
int filehandleReg = bytecode[pc++] & 0xFF;
591+
592+
Object val = registers[contentReg];
593+
RuntimeScalar fh = (RuntimeScalar) registers[filehandleReg];
594+
595+
RuntimeList list;
596+
if (val instanceof RuntimeList) {
597+
list = (RuntimeList) val;
598+
} else if (val instanceof RuntimeScalar) {
599+
// Convert scalar to single-element list
600+
list = new RuntimeList();
601+
list.add((RuntimeScalar) val);
602+
} else {
603+
list = new RuntimeList();
604+
}
605+
606+
// Call IOOperator.say()
607+
org.perlonjava.operators.IOOperator.say(list, fh);
574608
break;
575609
}
576610

@@ -679,6 +713,39 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
679713
break;
680714
}
681715

716+
// =================================================================
717+
// REFERENCE OPERATIONS
718+
// =================================================================
719+
720+
case Opcodes.CREATE_REF: {
721+
// Create reference: rd = rs.createReference()
722+
int rd = bytecode[pc++] & 0xFF;
723+
int rs = bytecode[pc++] & 0xFF;
724+
RuntimeBase value = registers[rs];
725+
registers[rd] = value.createReference();
726+
break;
727+
}
728+
729+
case Opcodes.DEREF: {
730+
// Dereference: rd = rs (dereferencing depends on context)
731+
// For now, just copy the reference - proper dereferencing
732+
// is context-dependent and handled by specific operators
733+
int rd = bytecode[pc++] & 0xFF;
734+
int rs = bytecode[pc++] & 0xFF;
735+
registers[rd] = registers[rs];
736+
break;
737+
}
738+
739+
case Opcodes.GET_TYPE: {
740+
// Get type: rd = new RuntimeScalar(rs.type)
741+
int rd = bytecode[pc++] & 0xFF;
742+
int rs = bytecode[pc++] & 0xFF;
743+
RuntimeScalar value = (RuntimeScalar) registers[rs];
744+
// RuntimeScalar.type is an int constant from RuntimeScalarType
745+
registers[rd] = new RuntimeScalar(value.type);
746+
break;
747+
}
748+
682749
// =================================================================
683750
// EVAL BLOCK SUPPORT
684751
// =================================================================
@@ -764,6 +831,39 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
764831
break;
765832
}
766833

834+
// =================================================================
835+
// STRING OPERATIONS
836+
// =================================================================
837+
838+
case Opcodes.JOIN: {
839+
// String join: rd = join(separator, list)
840+
int rd = bytecode[pc++] & 0xFF;
841+
int separatorReg = bytecode[pc++] & 0xFF;
842+
int listReg = bytecode[pc++] & 0xFF;
843+
844+
RuntimeScalar separator = (RuntimeScalar) registers[separatorReg];
845+
RuntimeBase list = registers[listReg];
846+
847+
// Call StringOperators.joinForInterpolation (doesn't warn on undef)
848+
registers[rd] = org.perlonjava.operators.StringOperators.joinForInterpolation(separator, list);
849+
break;
850+
}
851+
852+
case Opcodes.SELECT: {
853+
// Select default output filehandle: rd = IOOperator.select(list, SCALAR)
854+
int rd = bytecode[pc++] & 0xFF;
855+
int listReg = bytecode[pc++] & 0xFF;
856+
857+
RuntimeList list = (RuntimeList) registers[listReg];
858+
RuntimeScalar result = org.perlonjava.operators.IOOperator.select(list, RuntimeContextType.SCALAR);
859+
registers[rd] = result;
860+
break;
861+
}
862+
863+
// =================================================================
864+
// SLOW OPERATIONS
865+
// =================================================================
866+
767867
case Opcodes.SLOW_OP: {
768868
// Dispatch to slow operation handler
769869
// Format: [SLOW_OP] [slow_op_id] [operands...]

0 commit comments

Comments
 (0)