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
12 changes: 12 additions & 0 deletions dev/interpreter/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@
**Opcodes:** 0-157 (contiguous) for JVM tableswitch optimization
**Runtime:** 100% API compatibility with compiler (zero duplication)

### Testing Modes

**JPERL_EVAL_USE_INTERPRETER=1** - Forces all eval STRING to use the interpreter
- Used for testing interpreter implementation of operators in eval context
- Compiler still used for main code, only eval STRING uses interpreter
- Example: `JPERL_EVAL_USE_INTERPRETER=1 ./jperl test.pl`

**--interpreter** - Forces the interpreter EVERYWHERE
- All code (main and eval) runs in interpreter mode
- Used for full interpreter testing and development
- Example: `./jperl --interpreter test.pl`

## Core Files

- `Opcodes.java` - Opcode constants (0-157, contiguous)
Expand Down
741 changes: 705 additions & 36 deletions src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java

Large diffs are not rendered by default.

26 changes: 24 additions & 2 deletions src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java
Original file line number Diff line number Diff line change
Expand Up @@ -1707,10 +1707,19 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c

case Opcodes.CREATE_REF: {
// Create reference: rd = rs.createReference()
// For multi-element lists, create references to each element
int rd = bytecode[pc++];
int rs = bytecode[pc++];
RuntimeBase value = registers[rs];
registers[rd] = value.createReference();

// Special handling for RuntimeList
if (value instanceof RuntimeList list && list.elements.size() != 1) {
// Multi-element or empty list: create list of references
registers[rd] = list.createListReference();
} else {
// Single value or single-element list: create single reference
registers[rd] = value.createReference();
}
break;
}

Expand Down Expand Up @@ -2158,6 +2167,11 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
case Opcodes.EXIT:
pc = ScalarUnaryOpcodeHandler.execute(opcode, bytecode, pc, registers);
break;

case Opcodes.TR_TRANSLITERATE:
pc = SlowOpcodeHandler.executeTransliterate(bytecode, pc, registers);
break;

// GENERATED_HANDLERS_END

default:
Expand Down Expand Up @@ -2305,7 +2319,15 @@ private static int executeTypeOps(short opcode, short[] bytecode, int pc,
int rd = bytecode[pc++];
int rs = bytecode[pc++];
RuntimeBase value = registers[rs];
registers[rd] = value.createReference();

// Special handling for RuntimeList
if (value instanceof RuntimeList list && list.elements.size() != 1) {
// Multi-element or empty list: create list of references
registers[rd] = list.createListReference();
} else {
// Single value or single-element list: create single reference
registers[rd] = value.createReference();
}
return pc;
}

Expand Down
15 changes: 15 additions & 0 deletions src/main/java/org/perlonjava/interpreter/InterpretedCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -1231,6 +1231,21 @@ public String disassemble() {
case Opcodes.EXIT:
pc = ScalarUnaryOpcodeHandler.disassemble(opcode, bytecode, pc, sb);
break;

case Opcodes.TR_TRANSLITERATE:
rd = bytecode[pc++];
int searchReg = bytecode[pc++];
int replaceReg = bytecode[pc++];
int modifiersReg = bytecode[pc++];
int targetReg = bytecode[pc++];
int context = readInt(bytecode, pc);
pc += 2; // Skip the 2 shorts that make up the int
sb.append("TR_TRANSLITERATE r").append(rd).append(" = tr(r")
.append(searchReg).append(", r").append(replaceReg).append(", r")
.append(modifiersReg).append(") on r").append(targetReg)
.append(" ctx=").append(context).append("\n");
break;

// GENERATED_DISASM_END

default:
Expand Down
12 changes: 11 additions & 1 deletion src/main/java/org/perlonjava/interpreter/Opcodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -817,10 +817,20 @@ public class Opcodes {
* labelIndex: index into stringPool for label name (or -1 for unlabeled) */
public static final short REDO = 220;

/** Transliterate operator: Apply tr/// or y/// pattern to string
* Format: TR_TRANSLITERATE rd searchReg replaceReg modifiersReg targetReg context
* rd: destination register for result (count of transliterated characters)
* searchReg: register containing search pattern (RuntimeScalar)
* replaceReg: register containing replacement pattern (RuntimeScalar)
* modifiersReg: register containing modifiers string (RuntimeScalar)
* targetReg: register containing target variable to modify (RuntimeScalar)
* context: call context (SCALAR/LIST/VOID) */
public static final short TR_TRANSLITERATE = 221;

// =================================================================
// BUILT-IN FUNCTION OPCODES - after LASTOP
// Last manually-assigned opcode (for tool reference)
private static final short LASTOP = 220;
private static final short LASTOP = 221;

// =================================================================
// Generated by dev/tools/generate_opcode_handlers.pl
Expand Down
33 changes: 33 additions & 0 deletions src/main/java/org/perlonjava/interpreter/SlowOpcodeHandler.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.perlonjava.interpreter;

import org.perlonjava.operators.RuntimeTransliterate;
import org.perlonjava.runtime.*;

/**
Expand Down Expand Up @@ -970,6 +971,38 @@ public static int executeLength(
return pc;
}

/**
* TR_TRANSLITERATE: rd = tr///pattern applied to target
* Format: [TR_TRANSLITERATE] [rd] [searchReg] [replaceReg] [modifiersReg] [targetReg] [context]
* Effect: Applies transliteration pattern to target variable
* Returns: Count of transliterated characters
*/
public static int executeTransliterate(
short[] bytecode,
int pc,
RuntimeBase[] registers) {

int rd = bytecode[pc++];
int searchReg = bytecode[pc++];
int replaceReg = bytecode[pc++];
int modifiersReg = bytecode[pc++];
int targetReg = bytecode[pc++];

// Read context (4 bytes = 1 int)
int context = ((bytecode[pc++] & 0xFFFF) << 16) | (bytecode[pc++] & 0xFFFF);

RuntimeScalar search = (RuntimeScalar) registers[searchReg];
RuntimeScalar replace = (RuntimeScalar) registers[replaceReg];
RuntimeScalar modifiers = (RuntimeScalar) registers[modifiersReg];
RuntimeScalar target = (RuntimeScalar) registers[targetReg];

// Compile and apply transliteration
RuntimeTransliterate tr = RuntimeTransliterate.compile(search, replace, modifiers);
registers[rd] = tr.transliterate(target, context);

return pc;
}

private SlowOpcodeHandler() {
// Utility class - no instantiation
}
Expand Down
27 changes: 27 additions & 0 deletions src/main/java/org/perlonjava/runtime/RuntimeCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ public class RuntimeCode extends RuntimeBase implements RuntimeScalarReference {
public static final boolean EVAL_USE_INTERPRETER =
System.getenv("JPERL_EVAL_USE_INTERPRETER") != null;

/**
* Flag to control whether eval compilation errors should be printed to stderr.
* By default, eval failures are silent (errors only stored in $@).
*
* Set environment variable JPERL_EVAL_VERBOSE=1 to enable verbose error reporting.
* This is useful for debugging eval compilation issues, especially when testing
* the interpreter path.
*/
public static final boolean EVAL_VERBOSE =
System.getenv("JPERL_EVAL_VERBOSE") != null;

/**
* ThreadLocal storage for runtime values of captured variables during eval STRING compilation.
*
Expand Down Expand Up @@ -454,6 +465,14 @@ public static Class<?> evalStringHelper(RuntimeScalar code, String evalTag, Obje
RuntimeScalar err = GlobalVariable.getGlobalVariable("main::@");
err.set(e.getMessage());

// If EVAL_VERBOSE is set, print the error to stderr for debugging
if (EVAL_VERBOSE) {
System.err.println("eval compilation error: " + e.getMessage());
if (e.getCause() != null) {
System.err.println("Caused by: " + e.getCause().getMessage());
}
}

// Check if $SIG{__DIE__} handler is defined
RuntimeScalar sig = GlobalVariable.getGlobalHash("main::SIG").get("__DIE__");
if (sig.getDefinedBoolean()) {
Expand Down Expand Up @@ -801,6 +820,14 @@ public static RuntimeList evalStringWithInterpreter(
RuntimeScalar err = GlobalVariable.getGlobalVariable("main::@");
err.set(e.getMessage());

// If EVAL_VERBOSE is set, print the error to stderr for debugging
if (EVAL_VERBOSE) {
System.err.println("eval compilation error: " + e.getMessage());
if (e.getCause() != null) {
System.err.println("Caused by: " + e.getCause().getMessage());
}
}

// Check if $SIG{__DIE__} handler is defined
RuntimeScalar sig = GlobalVariable.getGlobalHash("main::SIG").get("__DIE__");
if (sig.getDefinedBoolean()) {
Expand Down