From ec610343fafe6835cca28576b183b2830848f963 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Mon, 23 Mar 2026 19:49:53 +0100 Subject: [PATCH] Fix use statement list context and sort method call parsing This commit addresses two related issues that were causing Module::Pluggable tests to fail: 1. **use statement arguments evaluated in wrong context** The expression passed to `use lib EXPR` was being evaluated in VOID context instead of LIST context. This caused expressions like: use lib (($path) =~ /^(.*)$/); to fail because the regex match was returning 1 (scalar success) instead of the captured path. Fixed by: - Adding an optional context parameter to `executePerlAST()` in PerlLanguageProvider.java - Adding an optional context parameter to `runSpecialBlock()` in SpecialBlockParser.java - Passing LIST context when executing `use` statement argument lists in StatementParser.java 2. **sort misparses method calls on bareword class names** `sort MyClass->method` was incorrectly parsed as `sort MyClass (->method)` where `MyClass` was treated as a comparison subroutine name. Fixed by checking if the identifier is followed by `->` after parsing it, and if so, backtracking to parse it as a method call expression instead. These fixes reduce Module::Pluggable test failures from 33/45 to 1/45. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../scriptengine/PerlLanguageProvider.java | 22 +++++++++++++--- .../frontend/parser/ParseMapGrepSort.java | 25 +++++++++++++++---- .../frontend/parser/SpecialBlockParser.java | 17 ++++++++++++- .../frontend/parser/StatementParser.java | 6 +++-- 4 files changed, 59 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/perlonjava/app/scriptengine/PerlLanguageProvider.java b/src/main/java/org/perlonjava/app/scriptengine/PerlLanguageProvider.java index 0c330d8d8..00bc90918 100644 --- a/src/main/java/org/perlonjava/app/scriptengine/PerlLanguageProvider.java +++ b/src/main/java/org/perlonjava/app/scriptengine/PerlLanguageProvider.java @@ -205,6 +205,7 @@ public static RuntimeList executePerlCode(CompilerOptions compilerOptions, /** * Executes the given Perl code using a syntax tree and returns the result. + * Uses VOID context by default. * * @param ast The abstract syntax tree representing the Perl code. * @param tokens The list of tokens representing the Perl code. @@ -214,6 +215,22 @@ public static RuntimeList executePerlCode(CompilerOptions compilerOptions, public static RuntimeList executePerlAST(Node ast, List tokens, CompilerOptions compilerOptions) throws Exception { + return executePerlAST(ast, tokens, compilerOptions, RuntimeContextType.VOID); + } + + /** + * Executes the given Perl code using a syntax tree with specified context. + * + * @param ast The abstract syntax tree representing the Perl code. + * @param tokens The list of tokens representing the Perl code. + * @param compilerOptions Compiler flags, file name and source code. + * @param contextType The context to use for execution (VOID, SCALAR, LIST). + * @return The result of the Perl code execution. + */ + public static RuntimeList executePerlAST(Node ast, + List tokens, + CompilerOptions compilerOptions, + int contextType) throws Exception { // Save the current scope so we can restore it after execution. ScopedSymbolTable savedCurrentScope = SpecialBlockParser.getCurrentScope(); @@ -229,7 +246,7 @@ public static RuntimeList executePerlAST(Node ast, globalSymbolTable.snapShot(), null, null, - RuntimeContextType.VOID, + contextType, true, null, compilerOptions, @@ -255,8 +272,7 @@ public static RuntimeList executePerlAST(Node ast, // Compile to executable (compiler or interpreter based on flag) RuntimeCode runtimeCode = compileToExecutable(ast, ctx); - // executePerlAST is always called from special blocks which use VOID context - return executeCode(runtimeCode, ctx, false, RuntimeContextType.VOID); + return executeCode(runtimeCode, ctx, false, contextType); } finally { // Restore the caller's scope if (savedCurrentScope != null) { diff --git a/src/main/java/org/perlonjava/frontend/parser/ParseMapGrepSort.java b/src/main/java/org/perlonjava/frontend/parser/ParseMapGrepSort.java index 0c45f4f15..91a1c94a4 100644 --- a/src/main/java/org/perlonjava/frontend/parser/ParseMapGrepSort.java +++ b/src/main/java/org/perlonjava/frontend/parser/ParseMapGrepSort.java @@ -28,12 +28,27 @@ static BinaryOperatorNode parseSort(Parser parser, LexerToken token) { if (nextToken.type == LexerTokenType.IDENTIFIER && !nextToken.text.equals("{") && !ParserTables.CORE_PROTOTYPES.containsKey(nextToken.text) && !ParsePrimary.isIsQuoteLikeOperator(nextToken.text)) { + // This could be a subroutine name for comparison (sort mysub LIST) + // or a class name for method call (sort MyClass->method) + // Save position and try to determine which + int identStart = parser.tokenIndex; String subName = IdentifierParser.parseSubroutineIdentifier(parser); - Node var = new OperatorNode("&", - new IdentifierNode(subName, parser.tokenIndex), parser.tokenIndex); - operand = ListParser.parseZeroOrMoreList(parser, 0, false, false, false, false); - operand.handle = var; - if (CompilerOptions.DEBUG_ENABLED) parser.ctx.logDebug("parseSort identifier: " + operand.handle + " : " + operand); + + // Check if followed by -> (method call) - if so, backtrack and parse as list + if (peek(parser).text.equals("->")) { + // This is a method call like "sort MyClass->method" + // Backtrack and parse the whole thing as a list expression + parser.tokenIndex = identStart; + operand = ListParser.parseZeroOrMoreList(parser, 0, false, false, false, false); + if (CompilerOptions.DEBUG_ENABLED) parser.ctx.logDebug("parseSort method call: " + operand); + } else { + // This is a comparison subroutine name + Node var = new OperatorNode("&", + new IdentifierNode(subName, parser.tokenIndex), parser.tokenIndex); + operand = ListParser.parseZeroOrMoreList(parser, 0, false, false, false, false); + operand.handle = var; + if (CompilerOptions.DEBUG_ENABLED) parser.ctx.logDebug("parseSort identifier: " + operand.handle + " : " + operand); + } } else { try { operand = ListParser.parseZeroOrMoreList(parser, 1, true, false, false, false); diff --git a/src/main/java/org/perlonjava/frontend/parser/SpecialBlockParser.java b/src/main/java/org/perlonjava/frontend/parser/SpecialBlockParser.java index 0f3153b8e..09591de1d 100644 --- a/src/main/java/org/perlonjava/frontend/parser/SpecialBlockParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/SpecialBlockParser.java @@ -98,6 +98,7 @@ static Node parseSpecialBlock(Parser parser) { /** * Executes a special block with the given block phase and block AST. + * Uses VOID context by default. * * @param parser The parser instance. * @param blockPhase The phase of the block (e.g., BEGIN, END). @@ -105,6 +106,19 @@ static Node parseSpecialBlock(Parser parser) { * @return A RuntimeList containing the result of the execution. */ static RuntimeList runSpecialBlock(Parser parser, String blockPhase, Node block) { + return runSpecialBlock(parser, blockPhase, block, RuntimeContextType.VOID); + } + + /** + * Executes a special block with the given block phase, block AST, and context. + * + * @param parser The parser instance. + * @param blockPhase The phase of the block (e.g., BEGIN, END). + * @param block The block AST to execute. + * @param contextType The context to use for execution (VOID, SCALAR, LIST). + * @return A RuntimeList containing the result of the execution. + */ + static RuntimeList runSpecialBlock(Parser parser, String blockPhase, Node block, int contextType) { int tokenIndex = parser.tokenIndex; // 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) result = PerlLanguageProvider.executePerlAST( new BlockNode(nodes, tokenIndex), parser.tokens, - parsedArgs); + parsedArgs, + contextType); } catch (PerlExitException e) { // exit() inside BEGIN block should terminate the program, not cause compilation error // Re-throw so it propagates to the CLI (Main.main()) which will call System.exit() diff --git a/src/main/java/org/perlonjava/frontend/parser/StatementParser.java b/src/main/java/org/perlonjava/frontend/parser/StatementParser.java index 11cef4e2c..4aa6becd3 100644 --- a/src/main/java/org/perlonjava/frontend/parser/StatementParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/StatementParser.java @@ -658,8 +658,10 @@ public static Node parseUseDeclaration(Parser parser, LexerToken token) { // call Module->import( LIST ) // or Module->unimport( LIST ) - // Execute the argument list immediately - RuntimeList args = runSpecialBlock(parser, "BEGIN", list); + // Execute the argument list immediately in LIST context + // This is necessary for expressions like: use lib ($path =~ /^(.*)$/); + // where the regex match must return captured groups, not just success/failure + RuntimeList args = runSpecialBlock(parser, "BEGIN", list, RuntimeContextType.LIST); if (CompilerOptions.DEBUG_ENABLED) ctx.logDebug("Use statement list: " + args); if (hasParentheses && args.isEmpty()) {