@@ -122,6 +122,9 @@ private static RuntimeBase doFile(RuntimeScalar runtimeScalar, boolean setINC, b
122122 // Variables for handling array references with state
123123 RuntimeCode codeRef = null ;
124124 RuntimeArray stateArgs = null ;
125+
126+ // Variable for storing @INC hook reference
127+ RuntimeScalar incHookRef = null ;
125128
126129 // ===== STEP 1: Handle ARRAY reference =====
127130 // Array format: [coderef|filehandle, state...]
@@ -331,6 +334,54 @@ else if (code == null) {
331334 }
332335
333336 for (RuntimeBase dir : inc ) {
337+ RuntimeScalar dirScalar = dir .scalar ();
338+
339+ // Check if this @INC entry is a CODE reference, ARRAY reference, or blessed object
340+ if (dirScalar .type == RuntimeScalarType .CODE ||
341+ dirScalar .type == RuntimeScalarType .REFERENCE ||
342+ dirScalar .type == RuntimeScalarType .ARRAYREFERENCE ||
343+ dirScalar .type == RuntimeScalarType .HASHREFERENCE ) {
344+
345+ RuntimeBase hookResult = tryIncHook (dirScalar , fileName );
346+ if (hookResult != null ) {
347+ // Hook returned something useful
348+ RuntimeScalar hookResultScalar = hookResult .scalar ();
349+
350+ // Check if it's a filehandle (GLOB) or array ref with filehandle
351+ RuntimeScalar filehandle = null ;
352+
353+ if (hookResultScalar .type == RuntimeScalarType .GLOB ||
354+ hookResultScalar .type == RuntimeScalarType .GLOBREFERENCE ) {
355+ filehandle = hookResultScalar ;
356+ } else if (hookResultScalar .type == RuntimeScalarType .ARRAYREFERENCE &&
357+ hookResultScalar .value instanceof RuntimeArray ) {
358+ RuntimeArray resultArray = (RuntimeArray ) hookResultScalar .value ;
359+ if (resultArray .size () > 0 ) {
360+ RuntimeScalar firstElem = resultArray .get (0 );
361+ if (firstElem .type == RuntimeScalarType .GLOB ||
362+ firstElem .type == RuntimeScalarType .GLOBREFERENCE ) {
363+ filehandle = firstElem ;
364+ }
365+ }
366+ }
367+
368+ if (filehandle != null ) {
369+ // Read content from the filehandle using the same method as STEP 4
370+ try {
371+ code = Readline .readline (filehandle , RuntimeContextType .LIST ).toString ();
372+ actualFileName = fileName ;
373+ incHookRef = dirScalar ;
374+ break ;
375+ } catch (Exception e ) {
376+ // Continue to next @INC entry
377+ }
378+ }
379+ }
380+ // If hook returned undef or we couldn't use the result, continue to next @INC entry
381+ continue ;
382+ }
383+
384+ // Original string handling for directory paths
334385 String dirName = dir .toString ();
335386 if (dirName .equals (GlobalContext .JAR_PERLLIB )) {
336387 // Try to find in jar at "src/main/perl/lib"
@@ -385,14 +436,15 @@ else if (code == null) {
385436 }
386437 }
387438
388- if (fullName == null ) {
439+ if (fullName == null && code == null ) {
389440 GlobalVariable .setGlobalVariable ("main::!" , "No such file or directory" );
390441 return new RuntimeScalar (); // return undef
391442 }
392443 }
393444
394445 CompilerOptions parsedArgs = new CompilerOptions ();
395446 parsedArgs .fileName = actualFileName ;
447+ parsedArgs .incHook = incHookRef ;
396448 if (code == null ) {
397449 try {
398450 code = FileUtils .readFileWithEncodingDetection (Paths .get (parsedArgs .fileName ), parsedArgs );
@@ -405,7 +457,23 @@ else if (code == null) {
405457
406458 // Set %INC if requested (before execution)
407459 if (setINC ) {
408- getGlobalHash ("main::INC" ).put (fileName , new RuntimeScalar (parsedArgs .fileName ));
460+ // Check if the hook already set %INC to a custom value
461+ RuntimeHash incHash = getGlobalHash ("main::INC" );
462+ RuntimeScalar existingIncValue = incHash .elements .get (fileName );
463+
464+ // Only set %INC if the hook didn't already set it
465+ if (existingIncValue == null || !existingIncValue .defined ().getBoolean ()) {
466+ // If we used an @INC hook, store the hook reference; otherwise store the filename
467+ RuntimeScalar incValue = (parsedArgs .incHook != null )
468+ ? parsedArgs .incHook
469+ : new RuntimeScalar (parsedArgs .fileName );
470+ incHash .put (fileName , incValue );
471+ } else if (parsedArgs .incHook != null ) {
472+ // Hook set %INC to a custom value - use that for actualFileName if it's a string
473+ if (existingIncValue .type == RuntimeScalarType .STRING ) {
474+ parsedArgs .fileName = existingIncValue .toString ();
475+ }
476+ }
409477 }
410478
411479 RuntimeList result ;
@@ -567,4 +635,102 @@ public static RuntimeScalar require(RuntimeScalar runtimeScalar) {
567635 // If module_true was disabled, result will be the module's actual return value
568636 return result ;
569637 }
638+
639+ /**
640+ * Try to call an @INC hook to load a module.
641+ *
642+ * <p>@INC can contain:
643+ * <ul>
644+ * <li>CODE reference: call it with ($coderef, $filename)</li>
645+ * <li>ARRAY reference: call $array->[0] with ($array, $filename)</li>
646+ * <li>Blessed object: call $obj->INC($filename) if the method exists</li>
647+ * </ul>
648+ *
649+ * <p>The hook can return:
650+ * <ul>
651+ * <li>undef: this hook can't handle it, continue to next @INC entry</li>
652+ * <li>A filehandle: read the module code from this filehandle</li>
653+ * <li>An array ref [$fh, \&filter, state...]: filehandle with optional filter and state</li>
654+ * </ul>
655+ *
656+ * @param hook The @INC hook (CODE, ARRAY, or blessed reference)
657+ * @param fileName The file name being required
658+ * @return The result from the hook (undef, filehandle, or array ref), or null if hook can't be called
659+ */
660+ private static RuntimeBase tryIncHook (RuntimeScalar hook , String fileName ) {
661+ RuntimeCode codeRef = null ;
662+ RuntimeScalar selfArg = hook ;
663+
664+ // First check if it's a blessed object (takes priority over plain refs)
665+ int blessIdInt = RuntimeScalarType .blessedId (hook );
666+
667+ // Case 1: Blessed object - try to call INC method
668+ if (blessIdInt != 0 ) {
669+ String blessId = NameNormalizer .getBlessStr (blessIdInt );
670+ if (blessId != null && !blessId .equals ("main" )) {
671+ // Try to find the INC method or AUTOLOAD
672+ try {
673+ // Try direct INC method first
674+ RuntimeScalar method = GlobalVariable .getGlobalCodeRef (blessId + "::INC" );
675+ if (method .defined ().getBoolean () && method .type == RuntimeScalarType .CODE ) {
676+ codeRef = (RuntimeCode ) method .value ;
677+ } else {
678+ // Try AUTOLOAD
679+ method = GlobalVariable .getGlobalCodeRef (blessId + "::AUTOLOAD" );
680+ if (method .defined ().getBoolean () && method .type == RuntimeScalarType .CODE ) {
681+ // Set up $AUTOLOAD variable
682+ GlobalVariable .getGlobalVariable (blessId + "::AUTOLOAD" ).set (blessId + "::INC" );
683+ codeRef = (RuntimeCode ) method .value ;
684+ }
685+ }
686+ } catch (Exception e ) {
687+ // Method not found, return null
688+ return null ;
689+ }
690+ }
691+ }
692+ // Case 2: CODE reference
693+ else if (hook .type == RuntimeScalarType .CODE ) {
694+ codeRef = (RuntimeCode ) hook .value ;
695+ }
696+ // Case 3: REFERENCE to CODE
697+ else if (hook .type == RuntimeScalarType .REFERENCE && hook .value instanceof RuntimeCode ) {
698+ codeRef = (RuntimeCode ) hook .value ;
699+ }
700+ // Case 4: ARRAY reference (not blessed) - call first element as coderef with array as $self
701+ else if (hook .type == RuntimeScalarType .ARRAYREFERENCE && hook .value instanceof RuntimeArray ) {
702+ RuntimeArray arr = (RuntimeArray ) hook .value ;
703+ if (arr .size () > 0 ) {
704+ RuntimeScalar firstElem = arr .get (0 );
705+ if (firstElem .type == RuntimeScalarType .CODE ) {
706+ codeRef = (RuntimeCode ) firstElem .value ;
707+ } else if (firstElem .type == RuntimeScalarType .REFERENCE && firstElem .value instanceof RuntimeCode ) {
708+ codeRef = (RuntimeCode ) firstElem .value ;
709+ }
710+ }
711+ }
712+
713+ if (codeRef == null ) {
714+ return null ;
715+ }
716+
717+ // Call the hook with ($self, $filename)
718+ RuntimeArray args = new RuntimeArray ();
719+ args .push (selfArg );
720+ args .push (new RuntimeScalar (fileName ));
721+
722+ try {
723+ RuntimeBase result = codeRef .apply (args , RuntimeContextType .SCALAR );
724+
725+ // If result is undef, return null to continue to next @INC entry
726+ if (result == null || !result .scalar ().defined ().getBoolean ()) {
727+ return null ;
728+ }
729+
730+ return result ;
731+ } catch (Exception e ) {
732+ // If hook throws an exception, continue to next @INC entry
733+ return null ;
734+ }
735+ }
570736}
0 commit comments