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
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ public static Node parseCoreOperator(Parser parser, LexerToken token, int startI
"glob", "gmtime", "hex", "int", "lc", "lcfirst", "length", "localtime", "log",
"oct", "ord", "pop", "pos", "prototype", "quotemeta", "rand", "ref", "reset",
"rmdir", "shift", "sin", "sleep", "sqrt", "srand", "study", "uc",
"ucfirst", "undef" -> OperatorParser.parseOperatorWithOneOptionalArgument(parser, token);
"ucfirst" -> OperatorParser.parseOperatorWithOneOptionalArgument(parser, token);
case "undef" -> OperatorParser.parseUndef(parser, token, currentIndex);
case "select" -> OperatorParser.parseSelect(parser, token, currentIndex);
case "stat", "lstat" -> OperatorParser.parseStat(parser, token, currentIndex);
case "readpipe" -> OperatorParser.parseReadpipe(parser);
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/org/perlonjava/parser/OperatorParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,22 @@ static OperatorNode parseDefined(Parser parser, LexerToken token, int currentInd
return new OperatorNode(token.text, operand, currentIndex);
}

static OperatorNode parseUndef(Parser parser, LexerToken token, int currentIndex) {
ListNode operand;
// Handle 'undef' operator with special parsing context
// Similar to 'defined', we need to prevent &subr from being auto-called
boolean parsingTakeReference = parser.parsingTakeReference;
parser.parsingTakeReference = true; // don't call `&subr` while parsing "Take reference"
operand = ListParser.parseZeroOrOneList(parser, 0);
parser.parsingTakeReference = parsingTakeReference;
if (operand.elements.isEmpty()) {
// `undef` without arguments returns undef
return new OperatorNode(token.text, null, currentIndex);
}

return new OperatorNode(token.text, operand, currentIndex);
}

static Node parseSpecialQuoted(Parser parser, LexerToken token, int startIndex) {
// Handle special-quoted domain-specific arguments
String operator = token.text;
Expand Down
35 changes: 33 additions & 2 deletions src/main/java/org/perlonjava/runtime/RuntimeGlob.java
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,35 @@ private void markGlobAsAssigned() {
* @return A RuntimeScalar representing the dereferenced value or reference. If the key
* is not recognized, an empty RuntimeScalar is returned.
*/
@Override
public RuntimeScalar hashDerefGet(RuntimeScalar index) {
// System.out.println("glob hashDerefGet " + index.toString());
return getGlobSlot(index);
}

@Override
public RuntimeScalar hashDerefGetNonStrict(RuntimeScalar index, String packageName) {
// For typeglobs, slot access doesn't need symbolic reference resolution
// Just access the slot directly
return getGlobSlot(index);
}

/**
* Get a typeglob slot (CODE, SCALAR, ARRAY, HASH, IO, FORMAT).
* This is the common implementation for both strict and non-strict contexts.
*/
private RuntimeScalar getGlobSlot(RuntimeScalar index) {
// System.out.println("glob getGlobSlot " + index.toString());
return switch (index.toString()) {
case "CODE" -> GlobalVariable.getGlobalCodeRef(this.globName);
case "CODE" -> {
// Only return CODE ref if the subroutine is actually defined
RuntimeScalar codeRef = GlobalVariable.getGlobalCodeRef(this.globName);
if (codeRef.type == RuntimeScalarType.CODE && codeRef.value instanceof RuntimeCode code) {
if (code.defined()) {
yield codeRef;
}
}
yield new RuntimeScalar(); // Return undef if code doesn't exist
}
case "IO" -> IO;
case "SCALAR" -> GlobalVariable.getGlobalVariable(this.globName);
case "ARRAY" -> {
Expand Down Expand Up @@ -211,10 +236,16 @@ public RuntimeScalar getIO() {

public RuntimeGlob setIO(RuntimeScalar io) {
this.IO = io;
// If the IO scalar contains a RuntimeIO, set its glob name
if (io.value instanceof RuntimeIO runtimeIO) {
runtimeIO.globName = this.globName;
}
return this;
}

public RuntimeGlob setIO(RuntimeIO io) {
// Set the glob name in the RuntimeIO for proper stringification
io.globName = this.globName;
this.IO = new RuntimeScalar(io);
return this;
}
Expand Down
12 changes: 11 additions & 1 deletion src/main/java/org/perlonjava/runtime/RuntimeIO.java
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,13 @@ protected boolean removeEldestEntry(Map.Entry<IOHandle, Boolean> eldest) {
*/
public DirectoryIO directoryIO;

/**
* The name of the glob that owns this IO handle (e.g., "main::STDOUT").
* Used for stringification when the filehandle is used in string context.
* Null if this handle is not associated with a named glob.
*/
public String globName;

/**
* Flag indicating if this handle has unflushed output.
* Used to determine when automatic flushing is needed.
Expand Down Expand Up @@ -757,11 +764,14 @@ private Set<StandardOpenOption> convertMode(String mode) {

/**
* Returns a string representation of this I/O handle.
* Format: GLOB(0xHASHCODE)
* Format: globName if known (e.g., "main::STDOUT"), otherwise GLOB(0xHASHCODE)
*
* @return string representation
*/
public String toString() {
if (globName != null) {
return globName;
}
return "GLOB(0x" + this.hashCode() + ")";
}

Expand Down
10 changes: 10 additions & 0 deletions src/main/java/org/perlonjava/runtime/RuntimeScalar.java
Original file line number Diff line number Diff line change
Expand Up @@ -1090,6 +1090,16 @@ public RuntimeScalar createReference() {
}

public RuntimeScalar undefine() {
// Special handling for CODE type - don't set the ref to undef,
// just clear the code from the global symbol table
if (type == RuntimeScalarType.CODE && value instanceof RuntimeCode) {
// Clear the code value but keep the type as CODE
this.value = new RuntimeCode(null, null);
// Invalidate the method resolution cache
org.perlonjava.mro.InheritanceResolver.invalidateCache();
return this;
}
// For all other types, set to undef
this.type = UNDEF;
this.value = null;
return this;
Expand Down
8 changes: 8 additions & 0 deletions src/main/perl/lib/IO/File.pm
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,14 @@ sub new {
$fh;
}

sub new_tmpfile {
my $class = shift;
@_ == 0 or croak "usage: $class->new_tmpfile()";
# TODO: Implement actual temporary file creation
# For now, return undef to indicate not supported
return undef;
}

################################################
## Open
##
Expand Down
15 changes: 15 additions & 0 deletions src/main/perl/lib/IO/Handle.pm
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,21 @@ sub getlines {
<$fh>;
}

sub gets {
my $fh = shift;
scalar <$fh>;
}

sub _open_mode_string {
my ($mode) = @_;
$mode =~ /^\+?(<|>>?)$/
or $mode =~ s/^r(\+?)$/$1</
or $mode =~ s/^w(\+?)$/$1>/
or $mode =~ s/^a(\+?)$/$1>>/
or croak "IO::Handle: bad open mode: $mode";
$mode;
}

sub write {
my $fh = shift;
my $buf = shift;
Expand Down
13 changes: 13 additions & 0 deletions src/main/perl/lib/IO/Seekable.pm
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,17 @@ sub tell {
tell($_[0]);
}

sub getpos {
@_ == 1 or croak 'usage: $io->getpos()';
my $fh = $_[0];
my $pos = tell($fh);
return undef if $pos < 0;
return $pos;
}

sub setpos {
@_ == 2 or croak 'usage: $io->setpos(POS)';
seek($_[0], $_[1], 0);
}

1;