diff --git a/src/main/java/org/perlonjava/parser/CoreOperatorResolver.java b/src/main/java/org/perlonjava/parser/CoreOperatorResolver.java index 415a17694..bca8cc7ed 100644 --- a/src/main/java/org/perlonjava/parser/CoreOperatorResolver.java +++ b/src/main/java/org/perlonjava/parser/CoreOperatorResolver.java @@ -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); diff --git a/src/main/java/org/perlonjava/parser/OperatorParser.java b/src/main/java/org/perlonjava/parser/OperatorParser.java index 99a03caee..72d13b64b 100644 --- a/src/main/java/org/perlonjava/parser/OperatorParser.java +++ b/src/main/java/org/perlonjava/parser/OperatorParser.java @@ -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; diff --git a/src/main/java/org/perlonjava/runtime/RuntimeGlob.java b/src/main/java/org/perlonjava/runtime/RuntimeGlob.java index 56b6b00cc..1ff52a01b 100644 --- a/src/main/java/org/perlonjava/runtime/RuntimeGlob.java +++ b/src/main/java/org/perlonjava/runtime/RuntimeGlob.java @@ -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" -> { @@ -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; } diff --git a/src/main/java/org/perlonjava/runtime/RuntimeIO.java b/src/main/java/org/perlonjava/runtime/RuntimeIO.java index b3a086025..a4890fe19 100644 --- a/src/main/java/org/perlonjava/runtime/RuntimeIO.java +++ b/src/main/java/org/perlonjava/runtime/RuntimeIO.java @@ -147,6 +147,13 @@ protected boolean removeEldestEntry(Map.Entry 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. @@ -757,11 +764,14 @@ private Set 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() + ")"; } diff --git a/src/main/java/org/perlonjava/runtime/RuntimeScalar.java b/src/main/java/org/perlonjava/runtime/RuntimeScalar.java index ec4115f83..68446d25f 100644 --- a/src/main/java/org/perlonjava/runtime/RuntimeScalar.java +++ b/src/main/java/org/perlonjava/runtime/RuntimeScalar.java @@ -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; diff --git a/src/main/perl/lib/IO/File.pm b/src/main/perl/lib/IO/File.pm index b7219fbe8..8b3ccf265 100644 --- a/src/main/perl/lib/IO/File.pm +++ b/src/main/perl/lib/IO/File.pm @@ -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 ## diff --git a/src/main/perl/lib/IO/Handle.pm b/src/main/perl/lib/IO/Handle.pm index 5ad204230..0ce3fccc3 100644 --- a/src/main/perl/lib/IO/Handle.pm +++ b/src/main/perl/lib/IO/Handle.pm @@ -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/^a(\+?)$/$1>>/ + or croak "IO::Handle: bad open mode: $mode"; + $mode; +} + sub write { my $fh = shift; my $buf = shift; diff --git a/src/main/perl/lib/IO/Seekable.pm b/src/main/perl/lib/IO/Seekable.pm index f9f1b7009..ebfe24892 100644 --- a/src/main/perl/lib/IO/Seekable.pm +++ b/src/main/perl/lib/IO/Seekable.pm @@ -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;