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
399 changes: 399 additions & 0 deletions .cognition/skills/port-cpan-module/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,399 @@
# Port CPAN Module to PerlOnJava

This skill guides you through porting a CPAN module with XS/C components to PerlOnJava using Java implementations.

## When to Use This Skill

- User asks to add a CPAN module to PerlOnJava
- User asks to port a Perl module with XS code
- User wants to implement Perl module functionality in Java

## Key Principles

1. **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.

2. **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.

3. **Credit original authors** - Always preserve the original AUTHORS and COPYRIGHT sections in the POD. Add a note that this is a PerlOnJava port.

## Overview

PerlOnJava supports three types of modules:
1. **Pure Perl modules** - Work directly, no Java needed
2. **Java-implemented modules (XSLoader)** - Replace XS/C with Java
3. **Built-in modules (GlobalContext)** - Internal only

**Most CPAN ports use type #2 (XSLoader).**

## Step-by-Step Process

### Phase 1: Analysis

1. **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
```

2. **Study the XS file thoroughly:**
- Look for `MODULE = ` and `PACKAGE = ` declarations
- Identify each XS function (appears after `void` or return type)
- Read the C code to understand algorithms and edge cases
- Note any platform-specific code (WIN32, etc.)
- Check for copyright notices to preserve

3. **Identify what needs Java implementation:**
- Functions defined in `.xs` files
- Functions that call C libraries (strftime, crypt, etc.)
- Functions loaded via `XSLoader::load()`

4. **Identify what can be reused as pure Perl (typically 70-90%):**
- Most accessor methods
- Helper/utility functions
- Overloaded operators
- Import/export logic
- Format translation maps
- Constants and configuration

5. **Check for dependencies:**
- Other modules the target depends on
- Whether those dependencies exist in PerlOnJava

6. **Check available Java libraries:**
- Review `pom.xml` and `build.gradle` for already-imported dependencies
- Common libraries already available: Gson, jnr-posix, jnr-ffi, SnakeYAML, etc.
- Consider if a Java library can replace the XS functionality directly

7. **Check existing PerlOnJava infrastructure:**
- `org.perlonjava.runtime.nativ.PosixLibrary` - JNR-POSIX wrapper for native calls
- `org.perlonjava.runtime.nativ.NativeUtils` - Cross-platform utilities with Windows fallbacks
- `org.perlonjava.runtime.operators.*` - Existing operator implementations

### Phase 2: Create Java Implementation

**File location:** `src/main/java/org/perlonjava/runtime/perlmodule/`

**Naming convention:** `Module::Name` → `ModuleName.java`
- `Time::Piece` → `TimePiece.java`
- `Digest::MD5` → `DigestMD5.java`
- `DBI` → `DBI.java`

**Basic structure:**
```java
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();
}
}
```

### Phase 3: Create Perl Wrapper

**File location:** `src/main/perl/lib/Module/Name.pm`

**Template:**
```perl
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, original@email.com

Additional Author, other@email.com (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
```

### Phase 4: Testing

1. **Create test file:** `src/test/resources/module_name.t`

2. **Compare with system Perl:**
```bash
# 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
```

3. **Build and verify:**
```bash
./gradlew build -x test
./jperl -e 'use Module::Name; ...'
```

## Common Patterns

### Reading XS Files

XS files have a specific structure:

```c
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:
- **Function name**: `_strftime` (usually prefixed with `_` for internal XS)
- **Parameters**: `fmt`, `epoch`, `islocal` with their C types
- **Default values**: `islocal = 1`
- **Return mechanism**: `ST(0)`, `RETVAL`, or stack manipulation

### Converting XS to Java

| 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()` |

### Using Existing Java Libraries

**Check `build.gradle` for available dependencies:**
```bash
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:**
```java
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:**
```java
// 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:**
```java
// Direct POSIX call (Unix only)
int uid = PosixLibrary.INSTANCE.getuid();

// Cross-platform with Windows fallback (preferred)
RuntimeScalar uid = NativeUtils.getuid(ctx);
```

### Returning Different Types

```java
// 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();
```

### Handling Context

```java
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;
}
}
```

## Checklist

### Pre-porting
- [ ] Fetch original `.pm` and `.xs` source
- [ ] Study XS code to understand C algorithms and edge cases
- [ ] Identify XS functions that need Java implementation
- [ ] Check dependencies exist in PerlOnJava
- [ ] Check `build.gradle`/`pom.xml` for usable Java libraries
- [ ] Check `nativ/` package for POSIX functionality
- [ ] Review existing similar modules for patterns

### Implementation
- [ ] Create `ModuleName.java` with XS replacements
- [ ] Create `Module/Name.pm` with pure Perl code
- [ ] Add proper author/copyright attribution
- [ ] Register all methods in `initialize()`

### Testing
- [ ] Build compiles without errors: `./gradlew build -x test`
- [ ] Basic functionality works: `./jperl -e 'use Module::Name; ...'`
- [ ] Compare output with system Perl
- [ ] Test edge cases identified in XS code

### Documentation
- [ ] Add POD with AUTHOR and COPYRIGHT sections
- [ ] Credit original authors

## Example: Time::Piece Port

**Files created:**
- `src/main/java/org/perlonjava/runtime/perlmodule/TimePiece.java`
- `src/main/java/org/perlonjava/runtime/perlmodule/POSIX.java` (for strftime)
- `src/main/perl/lib/Time/Piece.pm`
- `src/main/perl/lib/Time/Seconds.pm`

**XS 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%):**
- All accessor methods (sec, min, hour, year, etc.)
- Formatting helpers (ymd, hms, datetime)
- Julian day calculations
- Overloaded operators
- Import/export logic

## Troubleshooting

### "Can't load Java XS module"
- Check class name matches: `Module::Name` → `ModuleName.java`
- Verify `initialize()` method exists and is static
- Check package is `org.perlonjava.runtime.perlmodule`

### Method not found
- Ensure method is registered in `initialize()`
- Check method signature: `public static RuntimeList name(RuntimeArray args, int ctx)`

### Different output than system Perl
- Compare with fixed test values (not current time)
- Check locale handling
- Verify edge cases from XS comments

## References

- Module porting guide: `docs/guides/module-porting.md`
- Existing modules: `src/main/java/org/perlonjava/runtime/perlmodule/`
- Runtime types: `src/main/java/org/perlonjava/runtime/runtimetypes/`
Loading