Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 31 additions & 15 deletions src/main/java/org/perlonjava/app/cli/ArgumentParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,25 @@ private static void processShebangLine(String[] args, CompilerOptions parsedArgs
return;
}

if (processPerlShebangSwitches(shebangLine, parsedArgs)) {
return;
}

// Alternate interpreter (perlrun): if there is no word "perl"/"indir", exec the named program.
// Example: Inline's TestML tests start with "#!inc/bin/testml-cpan".
String[] tokens = shebangLine.split("\\s+");
if (tokens.length == 0) {
return;
}
if (isPerlOnJavaExecutable(Paths.get(tokens[0]))) {
// Same binary as this runtime (e.g. "#!/path/to/jperl"): compile here; do not re-exec.
return;
}
List<String> cmd = buildShebangCommand(tokens);
delegateToShebangInterpreter(args, cmd, index);
}

private static boolean processPerlShebangSwitches(String shebangLine, CompilerOptions parsedArgs) {
// perlrun: parsing of #! switches starts at a *word* "perl" or "indir".
// Substrings like "jperl" must NOT match (matches stock perl behavior).
Matcher perlWord = Pattern.compile("\\b(?:perl|indir)\\b", Pattern.CASE_INSENSITIVE).matcher(shebangLine);
Expand All @@ -348,21 +367,10 @@ private static void processShebangLine(String[] args, CompilerOptions parsedArgs
.filter(arg -> !arg.isEmpty())
.toArray(String[]::new);
processArgs(nonEmptyArgs, parsedArgs);
return;
return true;
}

// Alternate interpreter (perlrun): if there is no word "perl"/"indir", exec the named program.
// Example: Inline's TestML tests start with "#!inc/bin/testml-cpan".
String[] tokens = shebangLine.split("\\s+");
if (tokens.length == 0) {
return;
}
if (isPerlOnJavaExecutable(Paths.get(tokens[0]))) {
// Same binary as this runtime (e.g. "#!/path/to/jperl"): compile here; do not re-exec.
return;
}
List<String> cmd = buildShebangCommand(tokens);
delegateToShebangInterpreter(args, cmd, index);
return false;
}

/**
Expand Down Expand Up @@ -1242,6 +1250,10 @@ private static void modifyCodeBasedOnFlags(CompilerOptions parsedArgs) {

if (parsedArgs.discardLeadingGarbage) {
// '-x' extract Perl code after discarding leading garbage
if ("-e".equals(parsedArgs.fileName)) {
System.err.println("No Perl script found in input");
System.exit(1);
}
String fileContent = parsedArgs.code;
String[] lines = fileContent.split("\n");
boolean perlCodeStarted = false;
Expand All @@ -1250,8 +1262,12 @@ private static void modifyCodeBasedOnFlags(CompilerOptions parsedArgs) {
for (String line : lines) {
if (perlCodeStarted) {
perlCode.append(line).append("\n");
} else if (line.trim().equals("#!perl")) {
perlCodeStarted = true;
} else {
String trimmedLine = line.trim();
if (trimmedLine.startsWith("#!")
&& processPerlShebangSwitches(trimmedLine.substring(2).trim(), parsedArgs)) {
perlCodeStarted = true;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ public static RuntimeList executePerlAST(Node ast,

// Save the current scope so we can restore it after execution.
ScopedSymbolTable savedCurrentScope = SpecialBlockParser.getCurrentScope();
int savedCurrentScopeIndex = savedCurrentScope != null ? savedCurrentScope.currentScopeIndex() : -1;

// Save and clear the eval runtime context (same reason as executePerlCode)
RuntimeCode.EvalRuntimeContext savedEvalRuntimeContext =
Expand All @@ -293,24 +294,12 @@ public static RuntimeList executePerlAST(Node ast,
globalSymbolTable.addVariable("this", "", null);
globalSymbolTable.addVariable("@_", "our", null);
globalSymbolTable.addVariable("wantarray", "", null);
int executionFlagScopeIndex = globalSymbolTable.currentScopeIndex();

// Inherit $^H (strictOptions) from the caller's scope so BEGIN blocks
// can see and modify the enclosing scope's compile-time hints
// Inherit lexical pragma flags from the caller's scope so BEGIN blocks
// can see and modify the enclosing scope's compile-time hints.
if (savedCurrentScope != null) {
globalSymbolTable.setStrictOptions(savedCurrentScope.getStrictOptions());
// Inherit warning flags so ${^WARNING_BITS} returns correct values in BEGIN blocks
if (!savedCurrentScope.warningFlagsStack.isEmpty()) {
globalSymbolTable.warningFlagsStack.pop();
globalSymbolTable.warningFlagsStack.push((java.util.BitSet) savedCurrentScope.warningFlagsStack.peek().clone());
}
if (!savedCurrentScope.warningDisabledStack.isEmpty()) {
globalSymbolTable.warningDisabledStack.pop();
globalSymbolTable.warningDisabledStack.push((java.util.BitSet) savedCurrentScope.warningDisabledStack.peek().clone());
}
if (!savedCurrentScope.warningFatalStack.isEmpty()) {
globalSymbolTable.warningFatalStack.pop();
globalSymbolTable.warningFatalStack.push((java.util.BitSet) savedCurrentScope.warningFatalStack.peek().clone());
}
globalSymbolTable.copyFlagsFrom(savedCurrentScope, savedCurrentScopeIndex);
}

EmitterContext ctx = new EmitterContext(
Expand Down Expand Up @@ -346,12 +335,12 @@ public static RuntimeList executePerlAST(Node ast,

return executeCode(runtimeCode, ast, ctx, false, contextType);
} finally {
// Propagate $^H changes back to the caller's scope so subsequent
// code in the same lexical block sees the updated hints
// Propagate pragma changes back to the caller's scope so subsequent
// code in the same lexical block sees BEGIN-time feature/warning hints.
if (savedCurrentScope != null) {
savedCurrentScope.setStrictOptions(ctx.symbolTable.getStrictOptions());
savedCurrentScope.copyFlagsFrom(ctx.symbolTable, executionFlagScopeIndex);
// Also update per-call-site hints so caller()[8] and caller()[10] are correct
WarningBitsRegistry.setCallSiteHints(ctx.symbolTable.getStrictOptions());
WarningBitsRegistry.setCallSiteHints(savedCurrentScope.getStrictOptions());
WarningBitsRegistry.snapshotCurrentHintHash();
SpecialBlockParser.setCurrentScope(savedCurrentScope);
}
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,19 @@ private static void visitDefined(BytecodeCompiler bc, OperatorNode node) {
// defined(&name) - use stash lookup to match JVM backend/Perl 5 behavior
if (operand instanceof OperatorNode opNode && opNode.operator.equals("&")
&& opNode.operand instanceof IdentifierNode idNode) {
if (opNode.getAnnotation("parseTimeCodeRef") instanceof RuntimeScalar) {
bc.compileNode(opNode, -1, RuntimeContextType.SCALAR);
int codeRefReg = bc.lastResultReg;
int pkgIdx = bc.addToStringPool(bc.getCurrentPackage());
int rd = bc.allocateOutputRegister();
bc.emit(Opcodes.DEFINED_CODE_DYNAMIC);
bc.emitReg(rd);
bc.emitReg(codeRefReg);
bc.emit(pkgIdx);
bc.lastResultReg = rd;
return;
}

String subName = NameNormalizer.normalizeVariableName(
idNode.name, bc.getCurrentPackage());
int nameIdx = bc.addToStringPool(subName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ public RuntimeList apply(RuntimeArray args, int callContext) {
// Push args for getCallerArgs() support (used by List::Util::any/all/etc.)
// This matches what RuntimeCode.apply() does for JVM-compiled subs
RuntimeCode.pushArgs(args);
RuntimeCode.pushCallContext(callContext);
RuntimeCode.pushActiveCode(this);
// Push warning bits for FATAL warnings support
// This allows runtime code to check current warning context
Expand Down Expand Up @@ -368,6 +369,7 @@ public RuntimeList apply(String subroutineName, RuntimeArray args, int callConte
int effectiveContext = RuntimeCode.effectiveCallContext(this, callContext);
// Push args for getCallerArgs() support (used by List::Util::any/all/etc.)
RuntimeCode.pushArgs(args);
RuntimeCode.pushCallContext(callContext);
RuntimeCode.pushActiveCode(this);
// Push warning bits for FATAL warnings support
if (warningBitsString != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
import org.perlonjava.frontend.analysis.EmitterVisitor;
import org.perlonjava.frontend.astnode.*;
import org.perlonjava.runtime.operators.OperatorHandler;
import org.perlonjava.runtime.runtimetypes.GlobalVariable;
import org.perlonjava.runtime.runtimetypes.NameNormalizer;
import org.perlonjava.runtime.runtimetypes.PerlCompilerException;
import org.perlonjava.runtime.runtimetypes.RuntimeContextType;
import org.perlonjava.runtime.runtimetypes.RuntimeScalar;

public class EmitOperatorDeleteExists {
// Handles the 'delete' and 'exists' operators for hash elements.
Expand Down Expand Up @@ -197,7 +199,11 @@ static void handleDefined(OperatorNode node, String operator,
if (operator.equals("defined") && operatorNode.operator.equals("&")) {
if (CompilerOptions.DEBUG_ENABLED) emitterVisitor.ctx.logDebug("defined & " + operatorNode.operand);
if (operatorNode.operand instanceof IdentifierNode identifierNode) {
// exists &sub
if (operatorNode.getAnnotation("parseTimeCodeRef") instanceof RuntimeScalar codeRef) {
handleDefinedSubroutineCodeRef(emitterVisitor, codeRef);
return;
}
// defined &sub
handleExistsSubroutine(emitterVisitor, operator, identifierNode);
return;
}
Expand Down Expand Up @@ -258,6 +264,25 @@ private static void handleExistsSubroutine(EmitterVisitor emitterVisitor, String
EmitOperator.handleVoidContext(emitterVisitor);
}

private static void handleDefinedSubroutineCodeRef(EmitterVisitor emitterVisitor, RuntimeScalar codeRef) {
MethodVisitor mv = emitterVisitor.ctx.mv;
int codeRefId = GlobalVariable.registerCompiledCodeRef(codeRef);
mv.visitLdcInsn(codeRefId);
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/runtimetypes/GlobalVariable",
"getCompiledCodeRef",
"(I)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;",
false);
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/runtimetypes/GlobalVariable",
"definedGlobalCodeRefAsScalar",
"(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;",
false);
EmitOperator.handleVoidContext(emitterVisitor);
}

private static void handleExistsSubroutine(EmitterVisitor emitterVisitor, String operator, OperatorNode operatorNode) {
// exists &{"sub"}
operatorNode.accept(emitterVisitor.with(RuntimeContextType.SCALAR));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -981,25 +981,45 @@ public void copyFlagsFrom(ScopedSymbolTable source) {
throw new IllegalArgumentException("Source ScopedSymbolTable cannot be null.");
}

copyFlagsFrom(source, source.currentScopeIndex());
}

/**
* Copies flags from a specific source scope depth into this table's current scope.
*
* BEGIN/eval execution can temporarily leave the execution symbol table inside a
* nested scope after a caught parse error. Copying from the caller's original
* scope depth preserves pragma changes made in that lexical block without
* importing flags from an abandoned inner scope.
*
* @param source The source ScopedSymbolTable from which to copy the flags.
* @param sourceScopeIndex The lexical scope index to copy from.
*/
public void copyFlagsFrom(ScopedSymbolTable source, int sourceScopeIndex) {
if (source == null) {
throw new IllegalArgumentException("Source ScopedSymbolTable cannot be null.");
}
int index = Math.max(0, Math.min(sourceScopeIndex, source.warningFlagsStack.size() - 1));

// Copy warning flags
this.warningFlagsStack.pop();
this.warningFlagsStack.push((BitSet) source.warningFlagsStack.peek().clone());
this.warningFlagsStack.push((BitSet) source.warningFlagsStack.get(index).clone());

// Copy disabled warnings flags
this.warningDisabledStack.pop();
this.warningDisabledStack.push((BitSet) source.warningDisabledStack.peek().clone());
this.warningDisabledStack.push((BitSet) source.warningDisabledStack.get(index).clone());

// Copy fatal warnings flags
this.warningFatalStack.pop();
this.warningFatalStack.push((BitSet) source.warningFatalStack.peek().clone());
this.warningFatalStack.push((BitSet) source.warningFatalStack.get(index).clone());

// Copy feature flags
this.featureFlagsStack.pop();
this.featureFlagsStack.push(source.featureFlagsStack.peek());
this.featureFlagsStack.push(source.featureFlagsStack.get(index));

// Copy strict options
this.strictOptionsStack.pop();
this.strictOptionsStack.push(source.strictOptionsStack.peek());
this.strictOptionsStack.push(source.strictOptionsStack.get(index));
}

public record PackageInfo(String packageName, boolean isClass, String version) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,8 @@ public static RuntimeList isa(RuntimeArray args, int ctx) {
|| (argString.equals("ARRAY") && baseValue instanceof RuntimeArray)
|| (argString.equals("SCALAR") && baseValue instanceof RuntimeScalar)
|| (argString.equals("GLOB") && baseValue instanceof RuntimeGlob)
|| (argString.equals("FORMAT") && baseValue instanceof RuntimeFormat)) {
|| (argString.equals("FORMAT") && baseValue instanceof RuntimeFormat)
|| (argString.equals("CODE") && baseValue instanceof RuntimeCode)) {
return getScalarBoolean(true).getList();
}
}
Expand Down
Loading
Loading