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
31 changes: 31 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,37 @@ cyclonedxBom {
}
}

// Suppress CycloneDX schema validation warnings about unknown keywords
// The json-schema-validator library prints these directly to System.out
tasks.named('cyclonedxBom') {
doFirst {
// Save original streams
def originalOut = System.out
def originalErr = System.err
// Create filter that suppresses the "Unknown keyword" messages
def filterOut = new PrintStream(originalOut) {
@Override
void println(String x) {
if (x == null || (!x.contains("Unknown keyword") && !x.contains("NonValidationKeyword"))) {
super.println(x)
}
}
}
System.setOut(filterOut)
System.setErr(filterOut)
// Store originals for restoration
ext.originalOut = originalOut
ext.originalErr = originalErr
}
doLast {
// Restore original streams
if (ext.has('originalOut')) {
System.setOut(ext.originalOut)
System.setErr(ext.originalErr)
}
}
}

// Git info injection - injects commit ID and date into Configuration.java before compilation
// This ensures the built JAR contains accurate version information for -v output
def configFilePath = layout.projectDirectory.file('src/main/java/org/perlonjava/core/Configuration.java')
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/perlonjava/core/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public final class Configuration {
* Automatically populated by Gradle/Maven during build.
* DO NOT EDIT MANUALLY - this value is replaced at build time.
*/
public static final String gitCommitId = "c2748de62";
public static final String gitCommitId = "c8049e1a7";

/**
* Git commit date of the build (ISO format: YYYY-MM-DD).
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/perlonjava/frontend/parser/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ public class Parser {
public List<Node> classAdjustBlocks = new ArrayList<>();
// List to store heredoc nodes encountered during parsing.
private List<OperatorNode> heredocNodes = new ArrayList<>();
// When heredocs are processed before BEGIN blocks, this tracks where to skip to
// after the NEWLINE (past the heredoc content that was already consumed).
public int heredocSkipToIndex = -1;
// The specific NEWLINE token index that should trigger the skip.
public int heredocNewlineIndex = -1;

/**
* Constructs a Parser with the given context and tokens.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,35 @@ static Node parseSpecialBlock(Parser parser) {
// Consume the closing brace '}'
TokenUtils.consume(parser, LexerTokenType.OPERATOR, "}");

// Before executing BEGIN blocks, process any pending heredocs.
// This handles cases like: BEGIN { eval <<'END' } ... \n heredoc content \n END
// The heredoc content comes after the newline, but BEGIN must execute immediately.
// We need to fill in the heredoc content before BEGIN tries to use it.
if ("BEGIN".equals(blockName) && !parser.getHeredocNodes().isEmpty()) {
if (CompilerOptions.DEBUG_ENABLED) parser.ctx.logDebug("HEREDOC_BEGIN_FIX: Found " + parser.getHeredocNodes().size() + " pending heredocs after BEGIN block");
int savedIndex = parser.tokenIndex;
// Find the next NEWLINE token
int newlineIndex = -1;
for (int i = savedIndex; i < parser.tokens.size(); i++) {
if (parser.tokens.get(i).type == LexerTokenType.NEWLINE) {
newlineIndex = i;
break;
}
}
if (newlineIndex >= 0) {
if (CompilerOptions.DEBUG_ENABLED) parser.ctx.logDebug("HEREDOC_BEGIN_FIX: Processing at newlineIndex=" + newlineIndex + ", will skip to after heredocs");
// Temporarily advance to the newline to process heredocs
parser.tokenIndex = newlineIndex;
ParseHeredoc.parseHeredocAfterNewline(parser);
// Save where to skip to when we later encounter this specific newline
parser.heredocSkipToIndex = parser.tokenIndex;
parser.heredocNewlineIndex = newlineIndex;
if (CompilerOptions.DEBUG_ENABLED) parser.ctx.logDebug("HEREDOC_BEGIN_FIX: Set heredocSkipToIndex=" + parser.heredocSkipToIndex + ", heredocNewlineIndex=" + newlineIndex);
// Restore tokenIndex to continue parsing from after the '}'
parser.tokenIndex = savedIndex;
}
}

// ADJUST blocks in class context are not executed at parse time
// They are compiled as anonymous subs and stored for the constructor
if ("ADJUST".equals(blockName) && parser.isInClassBlock) {
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/org/perlonjava/frontend/parser/Whitespace.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ public static int skipWhitespace(Parser parser, int tokenIndex, List<LexerToken>
// Process heredocs before advancing past the NEWLINE
ParseHeredoc.parseHeredocAfterNewline(parser);
tokenIndex = parser.tokenIndex;
} else if (parser.heredocNewlineIndex == tokenIndex && parser.heredocSkipToIndex > tokenIndex) {
// Heredocs were already processed (e.g., before a BEGIN block).
// Skip past the heredoc content that was already consumed.
// Only skip when we reach the specific newline that was pre-processed.
tokenIndex = parser.heredocSkipToIndex;
parser.heredocSkipToIndex = -1;
parser.heredocNewlineIndex = -1;
}

tokenIndex++;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.perlonjava.runtime.io.CustomFileChannel;
import org.perlonjava.runtime.io.IOHandle;
import org.perlonjava.runtime.io.LayeredIOHandle;
import org.perlonjava.runtime.nativ.PosixLibrary;
import org.perlonjava.runtime.perlmodule.Warnings;
import org.perlonjava.runtime.runtimetypes.*;

Expand Down Expand Up @@ -266,6 +267,39 @@ public static RuntimeScalar fileTest(String operator, RuntimeScalar fileHandle)
return fileTest(operator, new RuntimeScalar(path.toString()));
}
}
// Special handling for -t on standard streams (STDIN, STDOUT, STDERR)
if (operator.equals("-t")) {
String globName = null;
if (fileHandle.value instanceof RuntimeGlob rg) {
globName = rg.globName;
} else if (fileHandle.value instanceof RuntimeIO rio) {
globName = rio.globName;
}
if (globName != null) {
int fd = -1;
if (globName.endsWith("::STDIN") || globName.equals("STDIN")) {
fd = 0;
} else if (globName.endsWith("::STDOUT") || globName.equals("STDOUT")) {
fd = 1;
} else if (globName.endsWith("::STDERR") || globName.equals("STDERR")) {
fd = 2;
}
if (fd >= 0) {
try {
boolean isTty = PosixLibrary.INSTANCE.isatty(fd) != 0;
getGlobalVariable("main::!").set(0);
return getScalarBoolean(isTty);
} catch (Exception e) {
// Fall back to System.console() check for fd 0
if (fd == 0) {
boolean isTty = System.console() != null;
getGlobalVariable("main::!").set(0);
return getScalarBoolean(isTty);
}
}
}
}
}
// Fallback for non-file handles (pipes, sockets, etc.)
getGlobalVariable("main::!").set(9);
updateLastStat(fileHandle, false, 9);
Expand Down
Loading