Skip to content

Replace Java Exporter with pure Perl implementation#347

Merged
fglock merged 5 commits into
masterfrom
experiment/pure-perl-exporter
Mar 21, 2026
Merged

Replace Java Exporter with pure Perl implementation#347
fglock merged 5 commits into
masterfrom
experiment/pure-perl-exporter

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Mar 21, 2026

Summary

This PR replaces the Java implementation of Exporter with the pure Perl version from Perl 5 core.

Changes

  • Removed Exporter.java - Java implementation no longer needed
  • Added Exporter.pm and Exporter/Heavy.pm from Perl 5 dist
  • Added generic inheritFrom(String parentModule) method to PerlModuleBase.java:
    • Loads the parent module via require if not already loaded
    • Adds parent to @ISA
    • initializeExporter() now calls inheritFrom("Exporter")
  • Updated GlobalContext.java to not initialize Java Exporter
  • Added design doc at dev/design/pure-perl-exporter.md

How It Works

Java modules that need Exporter functionality call initializeExporter() which:

  1. Requires Exporter.pm if not loaded
  2. Adds Exporter to the module's @ISA
  3. Module inherits Exporter::import from pure Perl

Known Issue

Lexical override of builtins (e.g., use Time::HiRes 'time') is not yet implemented. The time_hires_override.t test fails because the parser doesn't know to treat time as a subroutine call after import. This is documented in the design doc for Phase 2 implementation.

Test Plan

  • Basic exports work: use File::Basename qw(dirname)
  • Tag exports work: use Carp qw(:DEFAULT)
  • Constant pragma works: use constant X => 42
  • Data::Dumper, Getopt::Long work
  • use Time::HiRes 'time' - deferred to Phase 2

Generated with Devin

fglock and others added 4 commits March 21, 2026 10:40
- Remove Exporter.java and use Exporter.pm from Perl 5 core
- Add generic inheritFrom() method to PerlModuleBase for loading
  and inheriting from any Perl module on-demand
- Add Exporter.pm and Exporter/Heavy.pm to import config
- Update GlobalContext to not initialize Java Exporter

Known issue: Lexical override of builtins (e.g., `use Time::HiRes 'time'`)
not yet implemented - documented in dev/design/pure-perl-exporter.md

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Parser now checks if subroutine exists (via existsGlobalCodeRef) in addition
to checking isSubs flag. This allows imported subs like Time::HiRes::time
to override builtins when called without & prefix.

Since use statements run at BEGIN time before subsequent code is parsed,
imported subs are visible to the parser when it encounters calls to them.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
When taking a reference to a constant subroutine (\&name), return the
CODE reference instead of a reference to the constant value. This fixes
Exporter which does `*{$pkg::$sym} = \&{$src::$sym}` - the glob
assignment expects a CODE reference to work correctly.

This fixes regressions in op/do.t, io/scalar.t, op/stat_errors.t and
other tests that use Errno constants like ENOENT.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…balCodeRef

existsGlobalCodeRef returns true even for undefined stub subs, which
caused false positives when checking if an overridable builtin (like
open, readline) should be treated as a user-defined sub.

Use isGlobalCodeRefDefined which properly checks if the sub is actually
defined (has methodHandle or constantValue).

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fglock fglock force-pushed the experiment/pure-perl-exporter branch from d42447b to 6294edd Compare March 21, 2026 09:41
When initializeGlobals() is called, modules are initialized, which may
call require() to load other modules (like Exporter.pm). This triggers
re-entrant calls to initializeGlobals() with different compilerOptions,
overwriting special variables like $0, $/, and $\ with the module's values.

Fix by only setting these variables if they haven't been set yet, using
containsKey() checks before the first assignment.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fglock fglock merged commit 1abf60e into master Mar 21, 2026
2 checks passed
@fglock fglock deleted the experiment/pure-perl-exporter branch March 21, 2026 11:01
fglock added a commit that referenced this pull request Mar 21, 2026
Two fixes for run/switches.t regression introduced in PR #347:

1. Backticks now preserve exact output by reading raw bytes instead of
   using BufferedReader.readLine() which added extra trailing newlines

2. $^I is now initialized from compilerOptions.inPlaceExtension so the
   -i switch properly enables in-place editing with backup files

Test results: run/switches.t improved from 8/107 to 38/142 (baseline was 29/142)

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
fglock added a commit that referenced this pull request Mar 22, 2026
…ame (#349)

* Fix ClassCastException when CORE::GLOBAL::require is overridden

The bug occurred when:
1. CORE::GLOBAL::require was overridden
2. A file was loaded via do/require
3. That file contained a use statement

The parser assumed requireOp.operand was always a ListNode, but it could
be a NumberNode (e.g., for version requirements like 'use 5.008').

Added defensive instanceof checks in:
- ParsePrimary.java (root cause)
- EmitControlFlow.java
- EmitOperator.java
- EmitRegex.java
- CompileOperator.java

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* Add bundled Try::Tiny implementation and fix substr negative offset

Try::Tiny:
- Pure Perl implementation compatible with Try::Tiny 0.32
- All essential tests pass: basic.t (25/25), finally.t (30/30),
  context.t (25/25), erroneous_usage.t (8/8)
- Supports nested try/catch/finally, multiple finally blocks
- Properly re-throws if catch block dies (after finally runs)
- Warning format matches original for fatal finally blocks

Operator.java (substr):
- Fix negative offset behavior to match Perl 5 semantics
- Without explicit length: clip negative offsets to 0 (no warning)
- With explicit length: warn and return undef if offset is too negative
- Fixes unit/warnings.t test

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* Fix caller() to honor set_subname() for JVM-compiled code

- Add callerWithSub() method that takes __SUB__ parameter
- JVM backend now passes __SUB__ to caller() via handleCallerOperator
- For caller(0), if __SUB__ has a subName set by set_subname(), use it
- Interpreter already worked via InterpreterState tracking code.subName

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* Fix backticks trailing newline and -i switch regression

Two fixes for run/switches.t regression introduced in PR #347:

1. Backticks now preserve exact output by reading raw bytes instead of
   using BufferedReader.readLine() which added extra trailing newlines

2. $^I is now initialized from compilerOptions.inPlaceExtension so the
   -i switch properly enables in-place editing with backup files

Test results: run/switches.t improved from 8/107 to 38/142 (baseline was 29/142)

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant