Skip to content

Commit eb819fb

Browse files
authored
Merge pull request #53 from fglock/implement-alarm-signals
Implement @inc hooks for custom module loading
2 parents 2fce49b + 3118032 commit eb819fb

File tree

2 files changed

+170
-2
lines changed

2 files changed

+170
-2
lines changed

src/main/java/org/perlonjava/CompilerOptions.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import org.perlonjava.runtime.GlobalVariable;
44
import org.perlonjava.runtime.RuntimeArray;
5+
import org.perlonjava.runtime.RuntimeScalar;
56
import org.perlonjava.runtime.ScalarUtils;
67

78
import java.util.ArrayList;
@@ -71,6 +72,7 @@ public class CompilerOptions implements Cloneable {
7172
public boolean unicodeArgs = false; // -CA
7273
public boolean unicodeLocale = false; // -CL
7374
List<ArgumentParser.ModuleUseStatement> moduleUseStatements = new ArrayList<>(); // For -m -M
75+
public RuntimeScalar incHook = null; // For storing @INC hook reference
7476

7577
@Override
7678
public CompilerOptions clone() {

src/main/java/org/perlonjava/operators/ModuleOperators.java

Lines changed: 168 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)