Skip to content
87 changes: 75 additions & 12 deletions src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -938,7 +938,8 @@ void handleArrayKeyValueSlice(BinaryOperatorNode node, OperatorNode leftOp) {
// Compile indices into a list
List<Integer> pairRegs = new ArrayList<>();
for (Node indexElement : indicesNode.elements) {
indexElement.accept(this);
// Compile index in SCALAR context to ensure RuntimeScalar
compileNode(indexElement, -1, RuntimeContextType.SCALAR);
int indexReg = lastResultReg;

int valueReg = allocateRegister();
Expand Down Expand Up @@ -1118,7 +1119,8 @@ void handleArrayElementAccess(BinaryOperatorNode node, OperatorNode leftOp) {
// Handle single element access: $array[0]
if (indexNode.elements.size() == 1) {
Node indexExpr = indexNode.elements.get(0);
indexExpr.accept(this);
// Compile index in SCALAR context to ensure RuntimeScalar
compileNode(indexExpr, -1, RuntimeContextType.SCALAR);
int indexReg = lastResultReg;

// Emit ARRAY_GET opcode
Expand Down Expand Up @@ -1197,6 +1199,28 @@ void handleArraySlice(BinaryOperatorNode node, OperatorNode leftOp) {
throwCompilerException("Array slice requires array or array reference");
return;
}
} else if (leftOp.operand instanceof BlockNode blockNode) {
// Array dereference slice with block: @{$arrayref}[indices]
// Compile the block to get the array reference
int savedContext = currentCallContext;
currentCallContext = RuntimeContextType.SCALAR;
blockNode.accept(this);
currentCallContext = savedContext;
int refReg = lastResultReg;

// Dereference to get the array
arrayReg = allocateRegister();
if (isStrictRefsEnabled()) {
emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex());
emitReg(arrayReg);
emitReg(refReg);
} else {
int pkgIdx = addToStringPool(getCurrentPackage());
emitWithToken(Opcodes.DEREF_ARRAY_NONSTRICT, node.getIndex());
emitReg(arrayReg);
emitReg(refReg);
emit(pkgIdx);
}
} else {
throwCompilerException("Array slice requires identifier or reference");
return;
Expand Down Expand Up @@ -1305,8 +1329,8 @@ void handleHashElementAccess(BinaryOperatorNode node, OperatorNode leftOp) {

lastResultReg = rd;
} else {
// Expression key - compile it
keyExpr.accept(this);
// Expression key - compile it in SCALAR context to ensure we get RuntimeScalar
compileNode(keyExpr, -1, RuntimeContextType.SCALAR);
int keyReg = lastResultReg;

// Emit HASH_GET opcode
Expand Down Expand Up @@ -1374,6 +1398,28 @@ void handleHashSlice(BinaryOperatorNode node, OperatorNode leftOp) {
emitReg(scalarRefReg);
emit(pkgIdx);
}
} else if (leftOp.operand instanceof BlockNode blockNode) {
// Hash dereference slice with block: @{$hashref}{keys}
// Compile the block to get the hash reference
int savedContext = currentCallContext;
currentCallContext = RuntimeContextType.SCALAR;
blockNode.accept(this);
currentCallContext = savedContext;
int refReg = lastResultReg;

// Dereference to get the hash
hashReg = allocateRegister();
if (isStrictRefsEnabled()) {
emitWithToken(Opcodes.DEREF_HASH, node.getIndex());
emitReg(hashReg);
emitReg(refReg);
} else {
int pkgIdx = addToStringPool(getCurrentPackage());
emitWithToken(Opcodes.DEREF_HASH_NONSTRICT, node.getIndex());
emitReg(hashReg);
emitReg(refReg);
emit(pkgIdx);
}
} else {
throwCompilerException("Hash slice requires hash variable or reference");
return;
Expand Down Expand Up @@ -1401,7 +1447,7 @@ void handleHashSlice(BinaryOperatorNode node, OperatorNode leftOp) {
emit(keyIdx);
keyRegs.add(keyReg);
} else {
// Expression key
// Expression key - use default context to allow arrays to expand
keyElement.accept(this);
keyRegs.add(lastResultReg);
}
Expand Down Expand Up @@ -1495,6 +1541,7 @@ void handleHashKeyValueSlice(BinaryOperatorNode node, OperatorNode leftOp) {
emit(keyIdx);
keyRegs.add(keyReg);
} else {
// Expression key - use default context to allow arrays to expand
keyElement.accept(this);
keyRegs.add(lastResultReg);
}
Expand Down Expand Up @@ -1644,7 +1691,8 @@ void handleGeneralArrayAccess(BinaryOperatorNode node) {
// Handle single element access
if (indexNode.elements.size() == 1) {
Node indexExpr = indexNode.elements.get(0);
indexExpr.accept(this);
// Compile index in SCALAR context to ensure RuntimeScalar
compileNode(indexExpr, -1, RuntimeContextType.SCALAR);
int indexReg = lastResultReg;

// The base might be either:
Expand Down Expand Up @@ -1714,8 +1762,8 @@ void handleGeneralHashAccess(BinaryOperatorNode node) {
emitReg(keyReg);
emit(keyIdx);
} else {
// Expression key - compile it
keyExpr.accept(this);
// Expression key - compile it in SCALAR context to ensure RuntimeScalar
compileNode(keyExpr, -1, RuntimeContextType.SCALAR);
keyReg = lastResultReg;
}

Expand Down Expand Up @@ -1805,7 +1853,7 @@ void handlePushUnshift(BinaryOperatorNode node) {
} else if (leftOp.operator.equals("@") && !(leftOp.operand instanceof IdentifierNode)) {
// Array dereference: @$arrayref or @{expr}
// Evaluate the operand expression to get the reference, then deref
leftOp.operand.accept(this);
compileNode(leftOp.operand, -1, RuntimeContextType.SCALAR);
int refReg = lastResultReg;

// Dereference to get the array
Expand Down Expand Up @@ -3519,8 +3567,8 @@ void compileVariableReference(OperatorNode node, String op) {
emitReg(rd);
emit(nameIdx);
lastResultReg = rd;
} else if (node.operand instanceof OperatorNode) {
node.operand.accept(this);
} else if (node.operand instanceof OperatorNode || node.operand instanceof BlockNode) {
compileNode(node.operand, -1, RuntimeContextType.SCALAR);
int refReg = lastResultReg;
int rd = allocateOutputRegister();
int pkgIdx = addToStringPool(getCurrentPackage());
Expand All @@ -3537,7 +3585,7 @@ void compileVariableReference(OperatorNode node, String op) {
throwCompilerException("Unsupported * operand: " + node.operand.getClass().getSimpleName());
}
} else if (op.equals("&")) {
// Code reference: &subname
// Code reference: &subname or &{expr}
// Gets a reference to a named subroutine
if (node.operand instanceof IdentifierNode idNode) {
String subName = idNode.name;
Expand All @@ -3555,6 +3603,21 @@ void compileVariableReference(OperatorNode node, String op) {
emitReg(rd);
emit(nameIdx);

lastResultReg = rd;
} else if (node.operand instanceof BlockNode || node.operand instanceof OperatorNode) {
// Dynamic code reference: &{$name} or &$name
// Compile the expression to get the name/value, then dereference as code
compileNode(node.operand, -1, RuntimeContextType.SCALAR);
int valueReg = lastResultReg;

// Use CODE_DEREF_NONSTRICT to look up the code reference
int rd = allocateOutputRegister();
int pkgIdx = addToStringPool(getCurrentPackage());
emit(Opcodes.CODE_DEREF_NONSTRICT);
emitReg(rd);
emitReg(valueReg);
emit(pkgIdx);

lastResultReg = rd;
} else {
throwCompilerException("Unsupported & operand: " + node.operand.getClass().getSimpleName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,16 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
RuntimeScalar codeRef = (codeRefBase instanceof RuntimeScalar)
? (RuntimeScalar) codeRefBase
: codeRefBase.scalar();

// Dereference symbolic code references using current package
// This matches the JVM backend's call to codeDerefNonStrict()
// Only call for STRING/BYTE_STRING types (symbolic references)
// For CODE, REFERENCE, etc. let RuntimeCode.apply() handle errors
if (codeRef.type == RuntimeScalarType.STRING || codeRef.type == RuntimeScalarType.BYTE_STRING) {
String currentPkg = InterpreterState.currentPackage.get().toString();
codeRef = codeRef.codeDerefNonStrict(currentPkg);
}

RuntimeBase argsBase = registers[argsReg];

RuntimeArray callArgs;
Expand Down Expand Up @@ -1380,7 +1390,7 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
case Opcodes.EVAL_STRING, Opcodes.SELECT_OP, Opcodes.LOAD_GLOB, Opcodes.SLEEP_OP,
Opcodes.ALARM_OP, Opcodes.DEREF_GLOB, Opcodes.DEREF_GLOB_NONSTRICT,
Opcodes.LOAD_GLOB_DYNAMIC, Opcodes.DEREF_SCALAR_STRICT,
Opcodes.DEREF_SCALAR_NONSTRICT -> {
Opcodes.DEREF_SCALAR_NONSTRICT, Opcodes.CODE_DEREF_NONSTRICT -> {
pc = executeSpecialIO(opcode, bytecode, pc, registers, code);
}

Expand Down Expand Up @@ -2095,6 +2105,9 @@ private static int executeSpecialIO(int opcode, int[] bytecode, int pc,
case Opcodes.DEREF_SCALAR_NONSTRICT -> {
return SlowOpcodeHandler.executeDerefScalarNonStrict(bytecode, pc, registers, code);
}
case Opcodes.CODE_DEREF_NONSTRICT -> {
return SlowOpcodeHandler.executeCodeDerefNonStrict(bytecode, pc, registers, code);
}
default -> throw new RuntimeException("Unknown special I/O opcode: " + opcode);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -889,13 +889,15 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
bytecodeCompiler.emitReg(valueReg);

bytecodeCompiler.lastResultReg = valueReg;
} else if (leftOp.operator.equals("@") && leftOp.operand instanceof OperatorNode derefOp) {
// Array dereference assignment: @$r = ...
// The operand should be a scalar variable containing an array reference
} else if (leftOp.operator.equals("@") && (leftOp.operand instanceof OperatorNode || leftOp.operand instanceof BlockNode)) {
// Array dereference assignment: @$r = ... or @{expr} = ...
// The operand should evaluate to an array reference

if (derefOp.operator.equals("$")) {
// Compile the scalar to get the array reference
bytecodeCompiler.compileNode(derefOp, -1, rhsContext);
boolean isSimpleScalarDeref = leftOp.operand instanceof OperatorNode derefOp && derefOp.operator.equals("$");

if (isSimpleScalarDeref || leftOp.operand instanceof BlockNode) {
// Compile the operand to get the array reference
bytecodeCompiler.compileNode(leftOp.operand, -1, RuntimeContextType.SCALAR);
int scalarRefReg = bytecodeCompiler.lastResultReg;

// Dereference to get the actual array
Expand Down Expand Up @@ -936,6 +938,41 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
bytecodeCompiler.emitReg(arrayReg);
bytecodeCompiler.emitReg(valueReg);
bytecodeCompiler.lastResultReg = valueReg;
} else if (leftOp.operator.equals("%") && (leftOp.operand instanceof OperatorNode || leftOp.operand instanceof BlockNode)) {
// Hash dereference assignment: %$r = ... or %{expr} = ...
// The operand should evaluate to a hash reference

boolean isSimpleScalarDeref = leftOp.operand instanceof OperatorNode derefOp && derefOp.operator.equals("$");

if (isSimpleScalarDeref || leftOp.operand instanceof BlockNode) {
// Compile the operand to get the hash reference
bytecodeCompiler.compileNode(leftOp.operand, -1, RuntimeContextType.SCALAR);
int scalarRefReg = bytecodeCompiler.lastResultReg;

// Dereference to get the actual hash
int hashReg = bytecodeCompiler.allocateRegister();
if (bytecodeCompiler.isStrictRefsEnabled()) {
bytecodeCompiler.emitWithToken(Opcodes.DEREF_HASH, node.getIndex());
bytecodeCompiler.emitReg(hashReg);
bytecodeCompiler.emitReg(scalarRefReg);
} else {
int pkgIdx = bytecodeCompiler.addToStringPool(bytecodeCompiler.getCurrentPackage());
bytecodeCompiler.emitWithToken(Opcodes.DEREF_HASH_NONSTRICT, node.getIndex());
bytecodeCompiler.emitReg(hashReg);
bytecodeCompiler.emitReg(scalarRefReg);
bytecodeCompiler.emit(pkgIdx);
}

// Assign the value to the dereferenced hash
bytecodeCompiler.emit(Opcodes.HASH_SET_FROM_LIST);
bytecodeCompiler.emitReg(hashReg);
bytecodeCompiler.emitReg(valueReg);

// In list context, return the hash flattened; in other contexts return the hash
bytecodeCompiler.lastResultReg = hashReg;
} else {
bytecodeCompiler.throwCompilerException("Assignment to unsupported hash dereference");
}
} else {
if (leftOp.operator.equals("chop") || leftOp.operator.equals("chomp")) {
bytecodeCompiler.throwCompilerException("Can't modify " + leftOp.operator + " in scalar assignment");
Expand Down Expand Up @@ -1211,8 +1248,8 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
bytecodeCompiler.emit(keyIdx);
keyRegs.add(keyReg);
} else {
// Expression key
bytecodeCompiler.compileNode(keyElement, -1, rhsContext);
// Expression key - use default context to allow arrays to expand
keyElement.accept(bytecodeCompiler);
keyRegs.add(bytecodeCompiler.lastResultReg);
}
}
Expand Down Expand Up @@ -1330,8 +1367,8 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
bytecodeCompiler.emitReg(keyReg);
bytecodeCompiler.emit(keyIdx);
} else {
// Expression key: $hash{$var} or $hash{func()}
bytecodeCompiler.compileNode(keyElement, -1, rhsContext);
// Expression key: $hash{$var} or $hash{func()} - must be compiled in SCALAR context
bytecodeCompiler.compileNode(keyElement, -1, RuntimeContextType.SCALAR);
keyReg = bytecodeCompiler.lastResultReg;
}

Expand Down Expand Up @@ -1381,7 +1418,8 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
bytecodeCompiler.emitReg(keyReg);
bytecodeCompiler.emit(keyIdx);
} else {
bytecodeCompiler.compileNode(keyElement, -1, rhsContext);
// Expression key - must be compiled in SCALAR context
bytecodeCompiler.compileNode(keyElement, -1, RuntimeContextType.SCALAR);
keyReg = bytecodeCompiler.lastResultReg;
}
} else {
Expand Down Expand Up @@ -1657,6 +1695,26 @@ static int resolveArrayForDollarHash(BytecodeCompiler bytecodeCompiler, Operator
bytecodeCompiler.emit(pkgIdx);
}
return arrayReg;
} else if (dollarHashOp.operand instanceof BlockNode blockNode) {
// $#{BLOCK} = value - evaluate block to get array reference
int savedContext = bytecodeCompiler.currentCallContext;
bytecodeCompiler.currentCallContext = RuntimeContextType.SCALAR;
blockNode.accept(bytecodeCompiler);
bytecodeCompiler.currentCallContext = savedContext;
int refReg = bytecodeCompiler.lastResultReg;
int arrayReg = bytecodeCompiler.allocateRegister();
if (bytecodeCompiler.isStrictRefsEnabled()) {
bytecodeCompiler.emitWithToken(Opcodes.DEREF_ARRAY, dollarHashOp.getIndex());
bytecodeCompiler.emitReg(arrayReg);
bytecodeCompiler.emitReg(refReg);
} else {
int pkgIdx = bytecodeCompiler.addToStringPool(bytecodeCompiler.getCurrentPackage());
bytecodeCompiler.emitWithToken(Opcodes.DEREF_ARRAY_NONSTRICT, dollarHashOp.getIndex());
bytecodeCompiler.emitReg(arrayReg);
bytecodeCompiler.emitReg(refReg);
bytecodeCompiler.emit(pkgIdx);
}
return arrayReg;
}
bytecodeCompiler.throwCompilerException("$# assignment requires array variable");
return -1;
Expand Down
Loading