Skip to content

Commit 5693949

Browse files
committed
Merge master (includes PR 359 changes)
2 parents d79a7d6 + d41f340 commit 5693949

59 files changed

Lines changed: 13694 additions & 45 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/main/java/org/perlonjava/app/scriptengine/PerlLanguageProvider.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ public static RuntimeList executePerlCode(CompilerOptions compilerOptions,
205205

206206
/**
207207
* Executes the given Perl code using a syntax tree and returns the result.
208+
* Uses VOID context by default.
208209
*
209210
* @param ast The abstract syntax tree representing the Perl code.
210211
* @param tokens The list of tokens representing the Perl code.
@@ -214,6 +215,22 @@ public static RuntimeList executePerlCode(CompilerOptions compilerOptions,
214215
public static RuntimeList executePerlAST(Node ast,
215216
List<LexerToken> tokens,
216217
CompilerOptions compilerOptions) throws Exception {
218+
return executePerlAST(ast, tokens, compilerOptions, RuntimeContextType.VOID);
219+
}
220+
221+
/**
222+
* Executes the given Perl code using a syntax tree with specified context.
223+
*
224+
* @param ast The abstract syntax tree representing the Perl code.
225+
* @param tokens The list of tokens representing the Perl code.
226+
* @param compilerOptions Compiler flags, file name and source code.
227+
* @param contextType The context to use for execution (VOID, SCALAR, LIST).
228+
* @return The result of the Perl code execution.
229+
*/
230+
public static RuntimeList executePerlAST(Node ast,
231+
List<LexerToken> tokens,
232+
CompilerOptions compilerOptions,
233+
int contextType) throws Exception {
217234

218235
// Save the current scope so we can restore it after execution.
219236
ScopedSymbolTable savedCurrentScope = SpecialBlockParser.getCurrentScope();
@@ -229,7 +246,7 @@ public static RuntimeList executePerlAST(Node ast,
229246
globalSymbolTable.snapShot(),
230247
null,
231248
null,
232-
RuntimeContextType.VOID,
249+
contextType,
233250
true,
234251
null,
235252
compilerOptions,
@@ -255,8 +272,7 @@ public static RuntimeList executePerlAST(Node ast,
255272
// Compile to executable (compiler or interpreter based on flag)
256273
RuntimeCode runtimeCode = compileToExecutable(ast, ctx);
257274

258-
// executePerlAST is always called from special blocks which use VOID context
259-
return executeCode(runtimeCode, ctx, false, RuntimeContextType.VOID);
275+
return executeCode(runtimeCode, ctx, false, contextType);
260276
} finally {
261277
// Restore the caller's scope
262278
if (savedCurrentScope != null) {

src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3911,14 +3911,18 @@ void compileVariableReference(OperatorNode node, String op) {
39113911
// This will add the current package if no package is specified
39123912
subName = NameNormalizer.normalizeVariableName(subName, getCurrentPackage());
39133913

3914-
// Allocate register for code reference
3914+
// Cache the RuntimeScalar code reference at compile time.
3915+
// This matches Perl's behavior where the CV (code value) is cached
3916+
// in the compiled bytecode, surviving stash entry deletion.
3917+
RuntimeScalar codeRef = GlobalVariable.getGlobalCodeRef(subName);
3918+
3919+
// Allocate register and load from constant pool
39153920
int rd = allocateOutputRegister();
3916-
int nameIdx = addToStringPool(subName);
3921+
int constIdx = addToConstantPool(codeRef);
39173922

3918-
// Emit LOAD_GLOBAL_CODE
3919-
emit(Opcodes.LOAD_GLOBAL_CODE);
3923+
emit(Opcodes.LOAD_CONST);
39203924
emitReg(rd);
3921-
emit(nameIdx);
3925+
emit(constIdx);
39223926

39233927
lastResultReg = rd;
39243928
} else if (node.operand instanceof BlockNode || node.operand instanceof OperatorNode) {

src/main/java/org/perlonjava/backend/jvm/Dereference.java

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -365,14 +365,69 @@ static void handleArrayElementOperator(EmitterVisitor emitterVisitor, BinaryOper
365365
}
366366
}
367367
if (node.left instanceof ListNode list) { // ("a","b","c")[2]
368-
// transform to: ["a","b","c"]->[2]
369-
BinaryOperatorNode refNode = new BinaryOperatorNode("->",
370-
new ArrayLiteralNode(list.elements, list.getIndex()),
371-
node.right, node.tokenIndex);
372-
refNode.accept(emitterVisitor);
368+
// Use proper list slice semantics: evaluate list, then slice
369+
// This differs from array dereference because empty list returns empty, not undef
370+
if (CompilerOptions.DEBUG_ENABLED) emitterVisitor.ctx.logDebug("visit(BinaryOperatorNode) (list)[indices] - list slice");
371+
372+
// Evaluate the list
373+
list.accept(emitterVisitor.with(RuntimeContextType.LIST));
374+
375+
// Convert to RuntimeList if not already (handles RuntimeScalar case)
376+
emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
377+
"org/perlonjava/runtime/runtimetypes/RuntimeBase",
378+
"getList",
379+
"()Lorg/perlonjava/runtime/runtimetypes/RuntimeList;",
380+
false);
381+
382+
// Save the list to a local variable before evaluating indices.
383+
// This is necessary because indices may contain function calls that
384+
// generate complex bytecode with exception handlers, and the JVM
385+
// verifier requires consistent stack heights at merge points.
386+
int listVar = emitterVisitor.ctx.symbolTable.allocateLocalVariable();
387+
emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ASTORE, listVar);
388+
389+
// Evaluate the indices
390+
ListNode indices = ((ArrayLiteralNode) node.right).asListNode();
391+
indices.accept(emitterVisitor.with(RuntimeContextType.LIST));
392+
393+
// Save indices to local variable too
394+
int indicesVar = emitterVisitor.ctx.symbolTable.allocateLocalVariable();
395+
emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ASTORE, indicesVar);
396+
397+
// Load list and indices back, call RuntimeList.getSlice(indices)
398+
emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ALOAD, listVar);
399+
emitterVisitor.ctx.mv.visitVarInsn(Opcodes.ALOAD, indicesVar);
400+
emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
401+
"org/perlonjava/runtime/runtimetypes/RuntimeList",
402+
"getSlice",
403+
"(Lorg/perlonjava/runtime/runtimetypes/RuntimeList;)Lorg/perlonjava/runtime/runtimetypes/RuntimeList;",
404+
false);
405+
406+
// Handle context conversion
407+
if (emitterVisitor.ctx.contextType == RuntimeContextType.SCALAR) {
408+
emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeList",
409+
"scalar", "()Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false);
410+
} else if (emitterVisitor.ctx.contextType == RuntimeContextType.VOID) {
411+
emitterVisitor.ctx.mv.visitInsn(Opcodes.POP);
412+
}
373413
return;
374414
}
375415

416+
// For function calls and other expressions: (func())[index]
417+
// We need to use list slice semantics to handle empty lists correctly.
418+
// However, this should NOT apply to chained dereferences like $matrix[1][0]
419+
// where the first [1] returns a scalar (array reference) and the second
420+
// [0] should dereference it.
421+
//
422+
// List slice semantics apply when:
423+
// 1. The left side is a ListNode (literal list) - handled above
424+
// 2. The left side is a parenthesized function call (wantarray context)
425+
//
426+
// For now, we use the old transformation to ->[] for non-ListNode cases,
427+
// as most cases are array dereferences, not list slices.
428+
// TODO: Properly detect when the left side is a list-returning expression
429+
// vs. a scalar-returning expression.
430+
376431
// default: call `->[]`
377432
BinaryOperatorNode refNode = new BinaryOperatorNode("->", node.left, node.right, node.tokenIndex);
378433
refNode.accept(emitterVisitor);

src/main/java/org/perlonjava/core/Configuration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public final class Configuration {
3333
* Automatically populated by Gradle/Maven during build.
3434
* DO NOT EDIT MANUALLY - this value is replaced at build time.
3535
*/
36-
public static final String gitCommitId = "38832fe97";
36+
public static final String gitCommitId = "427621554";
3737

3838
/**
3939
* Git commit date of the build (ISO format: YYYY-MM-DD).

src/main/java/org/perlonjava/frontend/parser/IdentifierParser.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,11 @@ public static String parseComplexIdentifierInner(Parser parser, boolean insideBr
201201
if (insideBraces && firstChar == '*' && nextToken.text.equals("{")) {
202202
return null; // Force fallback to expression parsing for glob dereference
203203
}
204+
// Special case: & followed by { is subroutine call when inside braces
205+
// %{&{$code}} should be parsed as %{ &{$code} }, not %&{$code} (hash subscript on %&)
206+
if (insideBraces && firstChar == '&' && nextToken.text.equals("{")) {
207+
return null; // Force fallback to expression parsing for subroutine call
208+
}
204209
// Check if this is a leading single quote followed by an identifier ($'foo means $main::foo)
205210
if (firstChar == '\'' && (nextToken.type == LexerTokenType.IDENTIFIER || nextToken.type == LexerTokenType.NUMBER)) {
206211
// This is $'foo which means $main::foo

src/main/java/org/perlonjava/frontend/parser/ParseMapGrepSort.java

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,27 @@ static BinaryOperatorNode parseSort(Parser parser, LexerToken token) {
2828
if (nextToken.type == LexerTokenType.IDENTIFIER && !nextToken.text.equals("{")
2929
&& !ParserTables.CORE_PROTOTYPES.containsKey(nextToken.text)
3030
&& !ParsePrimary.isIsQuoteLikeOperator(nextToken.text)) {
31+
// This could be a subroutine name for comparison (sort mysub LIST)
32+
// or a class name for method call (sort MyClass->method)
33+
// Save position and try to determine which
34+
int identStart = parser.tokenIndex;
3135
String subName = IdentifierParser.parseSubroutineIdentifier(parser);
32-
Node var = new OperatorNode("&",
33-
new IdentifierNode(subName, parser.tokenIndex), parser.tokenIndex);
34-
operand = ListParser.parseZeroOrMoreList(parser, 0, false, false, false, false);
35-
operand.handle = var;
36-
if (CompilerOptions.DEBUG_ENABLED) parser.ctx.logDebug("parseSort identifier: " + operand.handle + " : " + operand);
36+
37+
// Check if followed by -> (method call) - if so, backtrack and parse as list
38+
if (peek(parser).text.equals("->")) {
39+
// This is a method call like "sort MyClass->method"
40+
// Backtrack and parse the whole thing as a list expression
41+
parser.tokenIndex = identStart;
42+
operand = ListParser.parseZeroOrMoreList(parser, 0, false, false, false, false);
43+
if (CompilerOptions.DEBUG_ENABLED) parser.ctx.logDebug("parseSort method call: " + operand);
44+
} else {
45+
// This is a comparison subroutine name
46+
Node var = new OperatorNode("&",
47+
new IdentifierNode(subName, parser.tokenIndex), parser.tokenIndex);
48+
operand = ListParser.parseZeroOrMoreList(parser, 0, false, false, false, false);
49+
operand.handle = var;
50+
if (CompilerOptions.DEBUG_ENABLED) parser.ctx.logDebug("parseSort identifier: " + operand.handle + " : " + operand);
51+
}
3752
} else {
3853
try {
3954
operand = ListParser.parseZeroOrMoreList(parser, 1, true, false, false, false);

src/main/java/org/perlonjava/frontend/parser/SpecialBlockParser.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,27 @@ static Node parseSpecialBlock(Parser parser) {
9898

9999
/**
100100
* Executes a special block with the given block phase and block AST.
101+
* Uses VOID context by default.
101102
*
102103
* @param parser The parser instance.
103104
* @param blockPhase The phase of the block (e.g., BEGIN, END).
104105
* @param block The block AST to execute.
105106
* @return A RuntimeList containing the result of the execution.
106107
*/
107108
static RuntimeList runSpecialBlock(Parser parser, String blockPhase, Node block) {
109+
return runSpecialBlock(parser, blockPhase, block, RuntimeContextType.VOID);
110+
}
111+
112+
/**
113+
* Executes a special block with the given block phase, block AST, and context.
114+
*
115+
* @param parser The parser instance.
116+
* @param blockPhase The phase of the block (e.g., BEGIN, END).
117+
* @param block The block AST to execute.
118+
* @param contextType The context to use for execution (VOID, SCALAR, LIST).
119+
* @return A RuntimeList containing the result of the execution.
120+
*/
121+
static RuntimeList runSpecialBlock(Parser parser, String blockPhase, Node block, int contextType) {
108122
int tokenIndex = parser.tokenIndex;
109123

110124
// Create AST nodes for setting up the capture variables and package declaration
@@ -252,7 +266,8 @@ static RuntimeList runSpecialBlock(Parser parser, String blockPhase, Node block)
252266
result = PerlLanguageProvider.executePerlAST(
253267
new BlockNode(nodes, tokenIndex),
254268
parser.tokens,
255-
parsedArgs);
269+
parsedArgs,
270+
contextType);
256271
} catch (PerlExitException e) {
257272
// exit() inside BEGIN block should terminate the program, not cause compilation error
258273
// Re-throw so it propagates to the CLI (Main.main()) which will call System.exit()

src/main/java/org/perlonjava/frontend/parser/StatementParser.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -658,8 +658,10 @@ public static Node parseUseDeclaration(Parser parser, LexerToken token) {
658658
// call Module->import( LIST )
659659
// or Module->unimport( LIST )
660660

661-
// Execute the argument list immediately
662-
RuntimeList args = runSpecialBlock(parser, "BEGIN", list);
661+
// Execute the argument list immediately in LIST context
662+
// This is necessary for expressions like: use lib ($path =~ /^(.*)$/);
663+
// where the regex match must return captured groups, not just success/failure
664+
RuntimeList args = runSpecialBlock(parser, "BEGIN", list, RuntimeContextType.LIST);
663665

664666
if (CompilerOptions.DEBUG_ENABLED) ctx.logDebug("Use statement list: " + args);
665667
if (hasParentheses && args.isEmpty()) {

src/main/java/org/perlonjava/runtime/perlmodule/Base.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ public static void initialize() {
2727
Base base = new Base();
2828
try {
2929
base.registerMethod("import", "importBase", ";$");
30+
// Set $VERSION so CPAN.pm can detect our bundled version
31+
GlobalVariable.getGlobalVariable("base::VERSION").set(new RuntimeScalar("2.27"));
3032
} catch (NoSuchMethodException e) {
3133
System.err.println("Warning: Missing Base method: " + e.getMessage());
3234
}

src/main/java/org/perlonjava/runtime/perlmodule/Parent.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ public static void initialize() {
2929
Parent parent = new Parent();
3030
try {
3131
parent.registerMethod("import", "importParent", ";$");
32+
// Set $VERSION so CPAN.pm can detect our bundled version
33+
GlobalVariable.getGlobalVariable("parent::VERSION").set(new RuntimeScalar("0.244"));
3234
} catch (NoSuchMethodException e) {
3335
System.err.println("Warning: Missing Parent method: " + e.getMessage());
3436
}

0 commit comments

Comments
 (0)