.cognition/skills/port-cpan-module/SKILL.md
# Port CPAN Module to PerlOnJava ## ⚠️⚠️⚠️ CRITICAL: NEVER USE `git stash` ⚠️⚠️⚠️ **DANGER: Changes are SILENTLY LOST when using git stash/stash pop!** - NEVER use `git stash` to temporarily revert changes - INSTEAD: Commit to a WIP branch or use `git diff > backup.patch` - This warning exists because completed work was lost during debugging This skill guides you through porting a CPAN module with XS/C components to PerlOnJava using Java implementations. ## When to Use This Skill - User as
npx skillsauth add fglock/perlonjava .cognition/skills/port-cpan-moduleInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
git stash ⚠️⚠️⚠️DANGER: Changes are SILENTLY LOST when using git stash/stash pop!
git stash to temporarily revert changesgit diff > backup.patchThis skill guides you through porting a CPAN module with XS/C components to PerlOnJava using Java implementations.
Reuse as much original code as possible - Most CPAN modules are 70-90% pure Perl. Only the XS/C portions need Java replacements. Copy the original .pm code and adapt minimally.
Always inspect the XS source - The .xs file reveals exactly what needs Java implementation. Study it to understand the C algorithms, edge cases, and expected behavior.
Credit original authors - Always preserve the original AUTHORS and COPYRIGHT sections in the POD. Add a note that this is a PerlOnJava port.
PerlOnJava supports three types of modules:
Most CPAN ports use type #2 (XSLoader).
Fetch the original module source:
https://fastapi.metacpan.org/v1/source/AUTHOR/Module-Version/Module.pm
https://fastapi.metacpan.org/v1/source/AUTHOR/Module-Version/Module.xs
Study the XS file thoroughly:
MODULE = and PACKAGE = declarationsvoid or return type)Identify what needs Java implementation:
.xs filesXSLoader::load()Identify what can be reused as pure Perl (typically 70-90%):
Check for dependencies:
Check available Java libraries:
pom.xml and build.gradle for already-imported dependenciesCheck existing PerlOnJava infrastructure:
org.perlonjava.runtime.nativ.PosixLibrary - JNR-POSIX wrapper for native callsorg.perlonjava.runtime.nativ.NativeUtils - Cross-platform utilities with Windows fallbacksorg.perlonjava.runtime.operators.* - Existing operator implementationsFile location: src/main/java/org/perlonjava/runtime/perlmodule/
Naming convention: Module::Name → ModuleName.java
Time::Piece → TimePiece.javaDigest::MD5 → DigestMD5.javaDBI → DBI.javaBasic structure:
package org.perlonjava.runtime.perlmodule;
import org.perlonjava.runtime.runtimetypes.*;
public class ModuleName extends PerlModuleBase {
public ModuleName() {
super("Module::Name", false); // false = not a pragma
}
public static void initialize() {
ModuleName module = new ModuleName();
try {
// Register methods - Perl name, Java method name (null = same), prototype
module.registerMethod("xs_function", null);
module.registerMethod("perl_name", "javaMethodName", null);
} catch (NoSuchMethodException e) {
System.err.println("Warning: Missing method: " + e.getMessage());
}
}
// Method signature: (RuntimeArray args, int ctx) -> RuntimeList
public static RuntimeList xs_function(RuntimeArray args, int ctx) {
// args.get(0) = first argument ($self for methods)
// ctx = RuntimeContextType.SCALAR, LIST, or VOID
String param = args.get(0).toString();
int number = args.get(1).getInt();
// Return value
return new RuntimeScalar(result).getList();
}
}
File location: src/main/perl/lib/Module/Name.pm
Template:
package Module::Name;
use strict;
use warnings;
our $VERSION = '1.00';
# Load Java implementation
use XSLoader;
XSLoader::load('Module::Name', $VERSION);
# Pure Perl code from original module goes here
# (accessors, helpers, overloads, etc.)
1;
__END__
=head1 NAME
Module::Name - Description
=head1 DESCRIPTION
This is a port of the CPAN Module::Name module for PerlOnJava.
=head1 AUTHOR
Original Author Name, [email protected]
Additional Author, [email protected] (if applicable)
=head1 COPYRIGHT AND LICENSE
Copyright YEAR, Original Copyright Holder.
This module is free software; you may distribute it under the same terms
as Perl itself.
=cut
ALWAYS use make commands. NEVER use raw mvn/gradlew commands.
| Command | What it does |
|---------|--------------|
| make | Build + run all unit tests (use before committing) |
| make dev | Build only, skip tests (for quick iteration during development) |
Create test file: src/test/resources/module_name.t
Compare with system Perl:
# Create test script
cat > /tmp/test.pl << 'EOF'
use Module::Name;
# test code
EOF
# Run with both
perl /tmp/test.pl
./jperl /tmp/test.pl
Build and verify:
make dev # Quick build (no tests)
./jperl -e 'use Module::Name; ...'
make # Full build with tests before committing
XS files have a specific structure:
MODULE = Time::Piece PACKAGE = Time::Piece
void
_strftime(fmt, epoch, islocal = 1)
char * fmt
time_t epoch
int islocal
CODE:
/* C implementation here */
ST(0) = sv_2mortal(newSVpv(result, len));
Key elements to identify:
_strftime (usually prefixed with _ for internal XS)fmt, epoch, islocal with their C typesislocal = 1ST(0), RETVAL, or stack manipulation| XS Pattern | Java Equivalent |
|------------|-----------------|
| SvIV(arg) | args.get(i).getInt() |
| SvNV(arg) | args.get(i).getDouble() |
| SvPV(arg, len) | args.get(i).toString() |
| newSViv(n) | new RuntimeScalar(n) |
| newSVnv(n) | new RuntimeScalar(n) |
| newSVpv(s, len) | new RuntimeScalar(s) |
| av_fetch(av, i, 0) | array.get(i) |
| hv_fetch(hv, k, len, 0) | hash.get(k) |
| RETVAL / ST(0) | return new RuntimeScalar(x).getList() |
Check build.gradle for available dependencies:
grep "implementation" build.gradle
Common libraries already in PerlOnJava:
| Java Library | Use Case | Example Module |
|--------------|----------|----------------|
| Gson | JSON parsing/encoding | Json.java |
| jnr-posix | Native POSIX calls | POSIX.java |
| jnr-ffi | Foreign function interface | Native bindings |
| SnakeYAML | YAML parsing | YAMLPP.java |
| TOML4J | TOML parsing | Toml.java |
| Java stdlib | Crypto, encoding, time | Various |
Example: JSON.java uses Gson directly:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public static RuntimeList encode_json(RuntimeArray args, int ctx) {
Gson gson = new GsonBuilder().create();
String json = gson.toJson(convertToJava(args.get(0)));
return new RuntimeScalar(json).getList();
}
Standard Java imports:
// Time operations
import java.time.*;
import java.time.format.DateTimeFormatter;
// Crypto
import java.security.MessageDigest;
// Encoding
import java.util.Base64;
import java.nio.charset.StandardCharsets;
// Native POSIX calls (with Windows fallbacks)
import org.perlonjava.runtime.nativ.PosixLibrary;
import org.perlonjava.runtime.nativ.NativeUtils;
Using PosixLibrary for native calls:
// Direct POSIX call (Unix only)
int uid = PosixLibrary.INSTANCE.getuid();
// Cross-platform with Windows fallback (preferred)
RuntimeScalar uid = NativeUtils.getuid(ctx);
// Scalar
return new RuntimeScalar(value).getList();
// List
RuntimeList result = new RuntimeList();
result.add(new RuntimeScalar(item1));
result.add(new RuntimeScalar(item2));
return result;
// Array reference
RuntimeArray arr = new RuntimeArray();
arr.push(new RuntimeScalar(item));
return arr.createReference().getList();
// Hash reference
RuntimeHash hash = new RuntimeHash();
hash.put("key", new RuntimeScalar(value));
return hash.createReference().getList();
public static RuntimeList myMethod(RuntimeArray args, int ctx) {
if (ctx == RuntimeContextType.SCALAR) {
// Return single value
return new RuntimeScalar(count).getList();
} else {
// Return list
RuntimeList result = new RuntimeList();
for (String item : items) {
result.add(new RuntimeScalar(item));
}
return result;
}
}
.pm and .xs sourcebuild.gradle/pom.xml for usable Java librariesnativ/ package for POSIX functionalityModuleName.java with XS replacementsModule/Name.pm with pure Perl codeinitialize()make dev (NEVER use raw mvn/gradlew)./jperl -e 'use Module::Name; ...'Files created:
src/main/java/org/perlonjava/runtime/perlmodule/TimePiece.javasrc/main/java/org/perlonjava/runtime/perlmodule/POSIX.java (for strftime)src/main/perl/lib/Time/Piece.pmsrc/main/perl/lib/Time/Seconds.pmXS functions replaced:
| XS Function | Java Implementation |
|-------------|---------------------|
| _strftime(fmt, epoch, islocal) | DateTimeFormatter with format mapping |
| _strptime(str, fmt, gmt, locale) | DateTimeFormatter.parse() |
| _tzset() | No-op (Java handles TZ) |
| _crt_localtime(epoch) | ZonedDateTime conversion |
| _crt_gmtime(epoch) | ZonedDateTime at UTC |
| _get_localization() | DateFormatSymbols |
| _mini_mktime(...) | LocalDateTime normalization |
Pure Perl reused (~80%):
Module::Name → ModuleName.javainitialize() method exists and is staticorg.perlonjava.runtime.perlmoduleinitialize()public static RuntimeList name(RuntimeArray args, int ctx)docs/guides/module-porting.mdsrc/main/java/org/perlonjava/runtime/perlmodule/src/main/java/org/perlonjava/runtime/runtimetypes/development
# PerlOnJava Debugging Skills and Architecture Knowledge This document captures key knowledge about PerlOnJava internals learned during debugging sessions. ## Variable Storage and Scoping ### Three Types of Variable Declarations 1. **`my` variables** - Lexical scope - Stored in JVM local variable slots during normal execution - When captured by closures: stored as closure fields or in GlobalVariable with IDs - Symbol table entry: `decl = "my"`, has `index` (JVM slot number) 2. **`o
development
# PerlOnJava Interpreter Developer Guide - name all test files /tmp/test.pl ## Quick Reference **Performance:** 46.84M ops/sec (1.75x slower than compiler ✓) **Opcodes:** 0-157 (contiguous) for JVM tableswitch optimization **Runtime:** 100% API compatibility with compiler (zero duplication) ### Testing Modes **JPERL_EVAL_USE_INTERPRETER=1** - Forces all eval STRING to use the interpreter - Used for testing interpreter implementation of operators in eval context - Compiler still used for mai
development
# Profile PerlOnJava ## ⚠️⚠️⚠️ CRITICAL: NEVER USE `git stash` ⚠️⚠️⚠️ **DANGER: Changes are SILENTLY LOST when using git stash/stash pop!** - NEVER use `git stash` to temporarily revert changes - INSTEAD: Commit to a WIP branch or use `git diff > backup.patch` - This warning exists because completed work was lost during debugging Profile and optimize PerlOnJava runtime performance using Java Flight Recorder. ## Git Workflow **IMPORTANT: Never push directly to master. Always use feature bra
development
Migrate from JNA to a modern native access library (eliminate sun.misc.Unsafe warnings)