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()) { diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java index b42ae4fa8..0b47daa96 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java @@ -2099,6 +2099,20 @@ public static RuntimeScalar createCodeReference(RuntimeScalar runtimeScalar, Str } } + // Handle GLOB type: \&{*glob} - get the code slot directly from the glob + // Globs stringify with a "*" prefix (e.g., "*main::test_sub") which would + // cause normalizeVariableName to look up the wrong name + if (runtimeScalar.type == RuntimeScalarType.GLOB) { + RuntimeGlob glob = (RuntimeGlob) runtimeScalar.value; + RuntimeScalar codeRef = GlobalVariable.getGlobalCodeRef(glob.globName); + + // Return a snapshot of the current code reference + RuntimeScalar snapshot = new RuntimeScalar(); + snapshot.type = codeRef.type; + snapshot.value = codeRef.value; + return snapshot; + } + String name = NameNormalizer.normalizeVariableName(runtimeScalar.toString(), packageName); // System.out.println("Creating code reference: " + name + " got: " + GlobalContext.getGlobalCodeRef(name)); RuntimeScalar codeRef = GlobalVariable.getGlobalCodeRef(name);