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
19 changes: 19 additions & 0 deletions dev/design/todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@
## Warnings to Implement
- `"my" variable $x masks earlier declaration in same scope` warning

## Typed Lexical Declarations (`my TYPE $var`)

Perl supports typed lexical declarations: `my Foo $x`, `my Foo::Bar $x`.
The type annotation is stored in the AST and currently ignored at runtime
(in Perl 5, it is used by the deprecated `fields` pragma for compile-time
hash-key checking via `use fields` / pseudo-hashes).

### Status
- Simple types work when the package is loaded: `my Foo $x` ✓
- Qualified types work when the package is loaded: `my Foo::Bar $x` ✓
- Types accepted without requiring package to be loaded ✓
(the type is saved as an AST annotation `"varType"` on the declaration node)
- `__PACKAGE__` and `__CLASS__` as type annotations ✓

### Future Work
- Validate the type at runtime (emit a warning/error if the class doesn't exist)
- Support `use fields` pragma for compile-time hash-key checking
- Use type annotations for optional JVM type hints or optimization

## More Difficult, and Low Impact
- `goto()` to jump to a label in the call stack
- Thread
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/org/perlonjava/backend/jvm/EmitVariable.java
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,16 @@ private static void emitStateInitialization(EmitterVisitor emitterVisitor, Binar
static void handleMyOperator(EmitterVisitor emitterVisitor, OperatorNode node) {
EmitterContext ctx = emitterVisitor.ctx;

// Emit runtime "No such class" check for typed declarations (my TYPE $var)
String varType = node.getAnnotation("varType") != null ? (String) node.getAnnotation("varType") : null;
if (varType != null) {
ctx.mv.visitLdcInsn(varType);
ctx.mv.visitMethodInsn(Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/runtimetypes/GlobalVariable",
"checkClassExists",
"(Ljava/lang/String;)V", false);
}

String operator = node.operator;
if (CompilerOptions.DEBUG_ENABLED) ctx.logDebug("handleMyOperator: operator=" + operator + ", operand type=" + (node.operand != null ? node.operand.getClass().getSimpleName() : "null") + ", contextType=" + ctx.contextType);
if (node.operand instanceof ListNode listNode) { // my ($a, $b) our ($a, $b)
Expand Down
4 changes: 2 additions & 2 deletions 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 = "f006e8c7b";
public static final String gitCommitId = "582f96865";

/**
* Git commit date of the build (ISO format: YYYY-MM-DD).
Expand All @@ -48,7 +48,7 @@ public final class Configuration {
* Parsed by App::perlbrew and other tools via: perl -V | grep "Compiled at"
* DO NOT EDIT MANUALLY - this value is replaced at build time.
*/
public static final String buildTimestamp = "Apr 13 2026 11:44:38";
public static final String buildTimestamp = "Apr 13 2026 13:52:26";

// Prevent instantiation
private Configuration() {
Expand Down
21 changes: 17 additions & 4 deletions src/main/java/org/perlonjava/frontend/parser/OperatorParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -365,12 +365,22 @@ static OperatorNode parseVariableDeclaration(Parser parser, String operator, int
TokenUtils.consume(parser); // consume __PACKAGE__/__CLASS__
varType = parser.ctx.symbolTable.getCurrentPackage();
} else {
// If a package name follows, then it is a type declaration
// If a package name follows, then it is a type declaration.
// In Perl, `my Foo::Bar $x` is valid when Foo::Bar is loaded.
// We accept the type name at parse time when unambiguously followed
// by a sigil ($, @, %, \) or opening paren, and defer the "No such class"
// check to runtime — matching Perl's behavior while handling the JVM
// architectural difference (entire file compiled before execution).
int currentIndex2 = parser.tokenIndex;
String packageName = IdentifierParser.parseSubroutineIdentifier(parser);
boolean packageExists = GlobalVariable.isPackageLoaded(packageName);
// System.out.println("maybe type: " + packageName + " " + packageExists);
if (packageExists) {
LexerToken afterType = peek(parser);
boolean followedBySigil = "$".equals(afterType.text) || "@".equals(afterType.text)
|| "%".equals(afterType.text) || "\\".equals(afterType.text)
|| "(".equals(afterType.text);
if (followedBySigil) {
// Unambiguously a type annotation (followed by a variable sigil or paren list)
varType = packageName;
} else if (GlobalVariable.isPackageLoaded(packageName)) {
varType = packageName;
} else {
// Backtrack
Expand Down Expand Up @@ -508,6 +518,9 @@ static OperatorNode parseVariableDeclaration(Parser parser, String operator, int
if (isDeclaredReference) {
decl.setAnnotation("isDeclaredReference", true);
}
if (varType != null) {
decl.setAnnotation("varType", varType);
}

// Initialize a list to store any attributes the declaration might have.
List<String> attributes = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,18 @@ static Node parseSubroutineCall(Parser parser, boolean isMethod) {
|| nextTok.text.equals(":"));
if (!terminator
&& !infixOp
&& nextTok.type != LexerTokenType.IDENTIFIER
&& (nextTok.type != LexerTokenType.IDENTIFIER
|| (subName.contains("::")
&& !nextTok.text.equals("or")
&& !nextTok.text.equals("and")
&& !nextTok.text.equals("not")
&& !nextTok.text.equals("if")
&& !nextTok.text.equals("unless")
&& !nextTok.text.equals("while")
&& !nextTok.text.equals("until")
&& !nextTok.text.equals("for")
&& !nextTok.text.equals("foreach")
&& !nextTok.text.equals("when")))
&& !nextTok.text.equals("->")
&& !nextTok.text.equals("=>")) {
// Check if this looks like indirect object syntax: method $object, args
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,65 @@ public interface FFMPosixInterface {
*/
int nativeDup(int fd);

// ==================== Low-level File Descriptor Functions ====================

/**
* Create a pipe.
* @param fds Array of at least 2 ints: fds[0] = read end, fds[1] = write end
* @return 0 on success, -1 on error (check errno)
*/
int pipe(int[] fds);

/**
* Duplicate a file descriptor.
* @param fd File descriptor to duplicate
* @return New file descriptor, or -1 on error (check errno)
*/
int dup(int fd);

/**
* Open a file.
* @param path File path
* @param flags Open flags (O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, etc.)
* @param mode Permission mode (used with O_CREAT)
* @return File descriptor, or -1 on error (check errno)
*/
int open(String path, int flags, int mode);

/**
* Close a file descriptor.
* @param fd File descriptor to close
* @return 0 on success, -1 on error (check errno)
*/
int close(int fd);

/**
* Read from a file descriptor.
* @param fd File descriptor
* @param buf Buffer to read into
* @param count Maximum number of bytes to read
* @return Number of bytes read, 0 at EOF, -1 on error (check errno)
*/
long read(int fd, byte[] buf, long count);

/**
* Write to a file descriptor.
* @param fd File descriptor
* @param buf Buffer to write from
* @param count Number of bytes to write
* @return Number of bytes written, -1 on error (check errno)
*/
long write(int fd, byte[] buf, long count);

/**
* Reposition read/write file offset.
* @param fd File descriptor
* @param offset Offset in bytes
* @param whence SEEK_SET (0), SEEK_CUR (1), or SEEK_END (2)
* @return Resulting offset from beginning of file, -1 on error
*/
long lseek(int fd, long offset, int whence);

// ==================== File Control Functions ====================

/**
Expand Down
Loading
Loading