Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/about/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ The following areas are currently under active development to enhance the functi
- Optimization: faster type resolution in Perl scalars.
- Optimization: `make` now runs tests in parallel.
- Optimization: A workaround is implemented to Java 64k bytes segment limit.
- New command line option: `--interpreter` to run PerlOnJava as an interpreter instead of JVM compiler.
- `./jperl --interpreter --disassemble -e 'print "Hello, World!\n"'`
- Planned release date: 2026-02-10.

- Work in Progress
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/perlonjava/ArgumentParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,10 @@ private static int processLongSwitches(String[] args, CompilerOptions parsedArgs
validateExclusiveOptions(parsedArgs, "disassemble");
parsedArgs.disassembleEnabled = true;
break;
case "--interpreter":
// Use bytecode interpreter instead of JVM compiler
parsedArgs.useInterpreter = true;
break;
case "--help":
// Print help message and exit
printHelp();
Expand Down Expand Up @@ -1066,6 +1070,7 @@ private static void printHelp() {
System.out.println(" --tokenize tokenize the input code");
System.out.println(" --parse parse the input code");
System.out.println(" --disassemble disassemble the generated code");
System.out.println(" --interpreter use bytecode interpreter instead of JVM compiler");
System.out.println(" -h, --help displays this help message");
System.out.println();
// System.out.println("Unicode/encoding flags for -C:");
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/perlonjava/CompilerOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* Fields:
* - debugEnabled: Enables debug mode, providing detailed logging during compilation.
* - disassembleEnabled: If true, the compiler will disassemble the generated bytecode.
* - useInterpreter: If true, use the bytecode interpreter instead of JVM compiler.
* - tokenizeOnly: If true, the compiler will only tokenize the input and stop.
* - parseOnly: If true, the compiler will only parse the input and stop.
* - compileOnly: If true, the compiler will compile the input but won't execute it.
Expand All @@ -34,6 +35,7 @@
public class CompilerOptions implements Cloneable {
public boolean debugEnabled = false;
public boolean disassembleEnabled = false;
public boolean useInterpreter = false;
public boolean tokenizeOnly = false;
public boolean parseOnly = false;
public boolean compileOnly = false;
Expand Down Expand Up @@ -94,6 +96,7 @@ public String toString() {
return "CompilerOptions{\n" +
" debugEnabled=" + debugEnabled + ",\n" +
" disassembleEnabled=" + disassembleEnabled + ",\n" +
" useInterpreter=" + useInterpreter + ",\n" +
" tokenizeOnly=" + tokenizeOnly + ",\n" +
" parseOnly=" + parseOnly + ",\n" +
" compileOnly=" + compileOnly + ",\n" +
Expand Down
116 changes: 116 additions & 0 deletions src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,35 @@ public void visit(IdentifierNode node) {

@Override
public void visit(BinaryOperatorNode node) {
// Handle print/say early (special handling for filehandle)
if (node.operator.equals("print") || node.operator.equals("say")) {
// print/say FILEHANDLE LIST
// left = filehandle reference (\*STDERR)
// right = list to print

// Compile the filehandle (left operand)
node.left.accept(this);
int filehandleReg = lastResultReg;

// Compile the content (right operand)
node.right.accept(this);
int contentReg = lastResultReg;

// Emit PRINT or SAY with both registers
emit(node.operator.equals("say") ? Opcodes.SAY : Opcodes.PRINT);
emit(contentReg);
emit(filehandleReg);

// print/say return 1 on success
int rd = allocateRegister();
emit(Opcodes.LOAD_INT);
emit(rd);
emitInt(1);

lastResultReg = rd;
return;
}

// Handle assignment separately (doesn't follow standard left-right-op pattern)
if (node.operator.equals("=")) {
// Special case: my $x = value
Expand Down Expand Up @@ -522,6 +551,16 @@ public void visit(BinaryOperatorNode node) {
// Note: CALL_SUB may return RuntimeControlFlowList
// The interpreter will handle control flow propagation
}
case "join" -> {
// String join: rd = join(separator, list)
// left (rs1) = separator (empty string for interpolation)
// right (rs2) = list of elements

emit(Opcodes.JOIN);
emit(rd);
emit(rs1);
emit(rs2);
}
default -> throw new RuntimeException("Unsupported operator: " + node.operator);
}

Expand Down Expand Up @@ -593,6 +632,50 @@ public void visit(OperatorNode node) {
} else if (op.equals("%")) {
// Hash variable dereference: %x
throw new RuntimeException("Hash variables not yet supported");
} else if (op.equals("*")) {
// Glob variable dereference: *x
if (node.operand instanceof IdentifierNode) {
IdentifierNode idNode = (IdentifierNode) node.operand;
String varName = idNode.name;

// Add package prefix if not present
if (!varName.contains("::")) {
varName = "main::" + varName;
}

// Allocate register for glob
int rd = allocateRegister();
int nameIdx = addToStringPool(varName);

// Emit SLOW_OP with SLOWOP_LOAD_GLOB
emitWithToken(Opcodes.SLOW_OP, node.getIndex());
emit(Opcodes.SLOWOP_LOAD_GLOB);
emit(rd);
emit(nameIdx);

lastResultReg = rd;
} else {
throw new RuntimeException("Unsupported * operand: " + node.operand.getClass().getSimpleName());
}
} else if (op.equals("\\")) {
// Reference operator: \$x, \@x, \%x, \*x, etc.
if (node.operand != null) {
// Evaluate the operand
node.operand.accept(this);
int valueReg = lastResultReg;

// Allocate register for reference
int rd = allocateRegister();

// Emit CREATE_REF
emit(Opcodes.CREATE_REF);
emit(rd);
emit(valueReg);

lastResultReg = rd;
} else {
throw new RuntimeException("Reference operator requires operand");
}
} else if (op.equals("say") || op.equals("print")) {
// say/print $x
if (node.operand != null) {
Expand Down Expand Up @@ -731,6 +814,39 @@ public void visit(OperatorNode node) {
emit(undefReg);
lastResultReg = undefReg;
}
} else if (op.equals("select")) {
// select FILEHANDLE or select()
// SELECT is a fast opcode (used in every print statement)
// Format: [SELECT] [rd] [rs_list]
// Effect: rd = IOOperator.select(registers[rs_list], SCALAR)

int rd = allocateRegister();

if (node.operand != null && node.operand instanceof ListNode) {
// select FILEHANDLE or select() with arguments
// Compile the operand (ListNode containing filehandle ref)
node.operand.accept(this);
int listReg = lastResultReg;

// Emit SELECT opcode
emitWithToken(Opcodes.SELECT, node.getIndex());
emit(rd);
emit(listReg);
} else {
// select() with no arguments - returns current filehandle
// Create empty list
emit(Opcodes.CREATE_LIST);
int listReg = allocateRegister();
emit(listReg);
emit(0); // count = 0

// Emit SELECT opcode
emitWithToken(Opcodes.SELECT, node.getIndex());
emit(rd);
emit(listReg);
}

lastResultReg = rd;
} else {
throw new UnsupportedOperationException("Unsupported operator: " + op);
}
Expand Down
116 changes: 108 additions & 8 deletions src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java
Original file line number Diff line number Diff line change
Expand Up @@ -559,18 +559,52 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
// =================================================================

case Opcodes.PRINT: {
// Print to STDOUT
int rs = bytecode[pc++] & 0xFF;
RuntimeScalar val = (RuntimeScalar) registers[rs];
System.out.print(val.toString());
// Print to filehandle
// Format: [PRINT] [rs_content] [rs_filehandle]
int contentReg = bytecode[pc++] & 0xFF;
int filehandleReg = bytecode[pc++] & 0xFF;

Object val = registers[contentReg];
RuntimeScalar fh = (RuntimeScalar) registers[filehandleReg];

RuntimeList list;
if (val instanceof RuntimeList) {
list = (RuntimeList) val;
} else if (val instanceof RuntimeScalar) {
// Convert scalar to single-element list
list = new RuntimeList();
list.add((RuntimeScalar) val);
} else {
list = new RuntimeList();
}

// Call IOOperator.print()
org.perlonjava.operators.IOOperator.print(list, fh);
break;
}

case Opcodes.SAY: {
// Say to STDOUT (print with newline)
int rs = bytecode[pc++] & 0xFF;
RuntimeScalar val = (RuntimeScalar) registers[rs];
System.out.println(val.toString());
// Say to filehandle
// Format: [SAY] [rs_content] [rs_filehandle]
int contentReg = bytecode[pc++] & 0xFF;
int filehandleReg = bytecode[pc++] & 0xFF;

Object val = registers[contentReg];
RuntimeScalar fh = (RuntimeScalar) registers[filehandleReg];

RuntimeList list;
if (val instanceof RuntimeList) {
list = (RuntimeList) val;
} else if (val instanceof RuntimeScalar) {
// Convert scalar to single-element list
list = new RuntimeList();
list.add((RuntimeScalar) val);
} else {
list = new RuntimeList();
}

// Call IOOperator.say()
org.perlonjava.operators.IOOperator.say(list, fh);
break;
}

Expand Down Expand Up @@ -679,6 +713,39 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
break;
}

// =================================================================
// REFERENCE OPERATIONS
// =================================================================

case Opcodes.CREATE_REF: {
// Create reference: rd = rs.createReference()
int rd = bytecode[pc++] & 0xFF;
int rs = bytecode[pc++] & 0xFF;
RuntimeBase value = registers[rs];
registers[rd] = value.createReference();
break;
}

case Opcodes.DEREF: {
// Dereference: rd = rs (dereferencing depends on context)
// For now, just copy the reference - proper dereferencing
// is context-dependent and handled by specific operators
int rd = bytecode[pc++] & 0xFF;
int rs = bytecode[pc++] & 0xFF;
registers[rd] = registers[rs];
break;
}

case Opcodes.GET_TYPE: {
// Get type: rd = new RuntimeScalar(rs.type)
int rd = bytecode[pc++] & 0xFF;
int rs = bytecode[pc++] & 0xFF;
RuntimeScalar value = (RuntimeScalar) registers[rs];
// RuntimeScalar.type is an int constant from RuntimeScalarType
registers[rd] = new RuntimeScalar(value.type);
break;
}

// =================================================================
// EVAL BLOCK SUPPORT
// =================================================================
Expand Down Expand Up @@ -764,6 +831,39 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
break;
}

// =================================================================
// STRING OPERATIONS
// =================================================================

case Opcodes.JOIN: {
// String join: rd = join(separator, list)
int rd = bytecode[pc++] & 0xFF;
int separatorReg = bytecode[pc++] & 0xFF;
int listReg = bytecode[pc++] & 0xFF;

RuntimeScalar separator = (RuntimeScalar) registers[separatorReg];
RuntimeBase list = registers[listReg];

// Call StringOperators.joinForInterpolation (doesn't warn on undef)
registers[rd] = org.perlonjava.operators.StringOperators.joinForInterpolation(separator, list);
break;
}

case Opcodes.SELECT: {
// Select default output filehandle: rd = IOOperator.select(list, SCALAR)
int rd = bytecode[pc++] & 0xFF;
int listReg = bytecode[pc++] & 0xFF;

RuntimeList list = (RuntimeList) registers[listReg];
RuntimeScalar result = org.perlonjava.operators.IOOperator.select(list, RuntimeContextType.SCALAR);
registers[rd] = result;
break;
}

// =================================================================
// SLOW OPERATIONS
// =================================================================

case Opcodes.SLOW_OP: {
// Dispatch to slow operation handler
// Format: [SLOW_OP] [slow_op_id] [operands...]
Expand Down
Loading