Skip to content

Commit f22d841

Browse files
authored
Merge pull request #296 from fglock/feature/time-piece-module
Add Time::Piece and Time::Seconds modules
2 parents 2d93e98 + ec0a103 commit f22d841

File tree

7 files changed

+2098
-1
lines changed

7 files changed

+2098
-1
lines changed
Lines changed: 399 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,399 @@
1+
# Port CPAN Module to PerlOnJava
2+
3+
This skill guides you through porting a CPAN module with XS/C components to PerlOnJava using Java implementations.
4+
5+
## When to Use This Skill
6+
7+
- User asks to add a CPAN module to PerlOnJava
8+
- User asks to port a Perl module with XS code
9+
- User wants to implement Perl module functionality in Java
10+
11+
## Key Principles
12+
13+
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.
14+
15+
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.
16+
17+
3. **Credit original authors** - Always preserve the original AUTHORS and COPYRIGHT sections in the POD. Add a note that this is a PerlOnJava port.
18+
19+
## Overview
20+
21+
PerlOnJava supports three types of modules:
22+
1. **Pure Perl modules** - Work directly, no Java needed
23+
2. **Java-implemented modules (XSLoader)** - Replace XS/C with Java
24+
3. **Built-in modules (GlobalContext)** - Internal only
25+
26+
**Most CPAN ports use type #2 (XSLoader).**
27+
28+
## Step-by-Step Process
29+
30+
### Phase 1: Analysis
31+
32+
1. **Fetch the original module source:**
33+
```
34+
https://fastapi.metacpan.org/v1/source/AUTHOR/Module-Version/Module.pm
35+
https://fastapi.metacpan.org/v1/source/AUTHOR/Module-Version/Module.xs
36+
```
37+
38+
2. **Study the XS file thoroughly:**
39+
- Look for `MODULE = ` and `PACKAGE = ` declarations
40+
- Identify each XS function (appears after `void` or return type)
41+
- Read the C code to understand algorithms and edge cases
42+
- Note any platform-specific code (WIN32, etc.)
43+
- Check for copyright notices to preserve
44+
45+
3. **Identify what needs Java implementation:**
46+
- Functions defined in `.xs` files
47+
- Functions that call C libraries (strftime, crypt, etc.)
48+
- Functions loaded via `XSLoader::load()`
49+
50+
4. **Identify what can be reused as pure Perl (typically 70-90%):**
51+
- Most accessor methods
52+
- Helper/utility functions
53+
- Overloaded operators
54+
- Import/export logic
55+
- Format translation maps
56+
- Constants and configuration
57+
58+
5. **Check for dependencies:**
59+
- Other modules the target depends on
60+
- Whether those dependencies exist in PerlOnJava
61+
62+
6. **Check available Java libraries:**
63+
- Review `pom.xml` and `build.gradle` for already-imported dependencies
64+
- Common libraries already available: Gson, jnr-posix, jnr-ffi, SnakeYAML, etc.
65+
- Consider if a Java library can replace the XS functionality directly
66+
67+
7. **Check existing PerlOnJava infrastructure:**
68+
- `org.perlonjava.runtime.nativ.PosixLibrary` - JNR-POSIX wrapper for native calls
69+
- `org.perlonjava.runtime.nativ.NativeUtils` - Cross-platform utilities with Windows fallbacks
70+
- `org.perlonjava.runtime.operators.*` - Existing operator implementations
71+
72+
### Phase 2: Create Java Implementation
73+
74+
**File location:** `src/main/java/org/perlonjava/runtime/perlmodule/`
75+
76+
**Naming convention:** `Module::Name``ModuleName.java`
77+
- `Time::Piece``TimePiece.java`
78+
- `Digest::MD5``DigestMD5.java`
79+
- `DBI``DBI.java`
80+
81+
**Basic structure:**
82+
```java
83+
package org.perlonjava.runtime.perlmodule;
84+
85+
import org.perlonjava.runtime.runtimetypes.*;
86+
87+
public class ModuleName extends PerlModuleBase {
88+
89+
public ModuleName() {
90+
super("Module::Name", false); // false = not a pragma
91+
}
92+
93+
public static void initialize() {
94+
ModuleName module = new ModuleName();
95+
try {
96+
// Register methods - Perl name, Java method name (null = same), prototype
97+
module.registerMethod("xs_function", null);
98+
module.registerMethod("perl_name", "javaMethodName", null);
99+
} catch (NoSuchMethodException e) {
100+
System.err.println("Warning: Missing method: " + e.getMessage());
101+
}
102+
}
103+
104+
// Method signature: (RuntimeArray args, int ctx) -> RuntimeList
105+
public static RuntimeList xs_function(RuntimeArray args, int ctx) {
106+
// args.get(0) = first argument ($self for methods)
107+
// ctx = RuntimeContextType.SCALAR, LIST, or VOID
108+
109+
String param = args.get(0).toString();
110+
int number = args.get(1).getInt();
111+
112+
// Return value
113+
return new RuntimeScalar(result).getList();
114+
}
115+
}
116+
```
117+
118+
### Phase 3: Create Perl Wrapper
119+
120+
**File location:** `src/main/perl/lib/Module/Name.pm`
121+
122+
**Template:**
123+
```perl
124+
package Module::Name;
125+
126+
use strict;
127+
use warnings;
128+
129+
our $VERSION = '1.00';
130+
131+
# Load Java implementation
132+
use XSLoader;
133+
XSLoader::load('Module::Name', $VERSION);
134+
135+
# Pure Perl code from original module goes here
136+
# (accessors, helpers, overloads, etc.)
137+
138+
1;
139+
140+
__END__
141+
142+
=head1 NAME
143+
144+
Module::Name - Description
145+
146+
=head1 DESCRIPTION
147+
148+
This is a port of the CPAN Module::Name module for PerlOnJava.
149+
150+
=head1 AUTHOR
151+
152+
Original Author Name, original@email.com
153+
154+
Additional Author, other@email.com (if applicable)
155+
156+
=head1 COPYRIGHT AND LICENSE
157+
158+
Copyright YEAR, Original Copyright Holder.
159+
160+
This module is free software; you may distribute it under the same terms
161+
as Perl itself.
162+
163+
=cut
164+
```
165+
166+
### Phase 4: Testing
167+
168+
1. **Create test file:** `src/test/resources/module_name.t`
169+
170+
2. **Compare with system Perl:**
171+
```bash
172+
# Create test script
173+
cat > /tmp/test.pl << 'EOF'
174+
use Module::Name;
175+
# test code
176+
EOF
177+
178+
# Run with both
179+
perl /tmp/test.pl
180+
./jperl /tmp/test.pl
181+
```
182+
183+
3. **Build and verify:**
184+
```bash
185+
./gradlew build -x test
186+
./jperl -e 'use Module::Name; ...'
187+
```
188+
189+
## Common Patterns
190+
191+
### Reading XS Files
192+
193+
XS files have a specific structure:
194+
195+
```c
196+
MODULE = Time::Piece PACKAGE = Time::Piece
197+
198+
void
199+
_strftime(fmt, epoch, islocal = 1)
200+
char * fmt
201+
time_t epoch
202+
int islocal
203+
CODE:
204+
/* C implementation here */
205+
ST(0) = sv_2mortal(newSVpv(result, len));
206+
```
207+
208+
Key elements to identify:
209+
- **Function name**: `_strftime` (usually prefixed with `_` for internal XS)
210+
- **Parameters**: `fmt`, `epoch`, `islocal` with their C types
211+
- **Default values**: `islocal = 1`
212+
- **Return mechanism**: `ST(0)`, `RETVAL`, or stack manipulation
213+
214+
### Converting XS to Java
215+
216+
| XS Pattern | Java Equivalent |
217+
|------------|-----------------|
218+
| `SvIV(arg)` | `args.get(i).getInt()` |
219+
| `SvNV(arg)` | `args.get(i).getDouble()` |
220+
| `SvPV(arg, len)` | `args.get(i).toString()` |
221+
| `newSViv(n)` | `new RuntimeScalar(n)` |
222+
| `newSVnv(n)` | `new RuntimeScalar(n)` |
223+
| `newSVpv(s, len)` | `new RuntimeScalar(s)` |
224+
| `av_fetch(av, i, 0)` | `array.get(i)` |
225+
| `hv_fetch(hv, k, len, 0)` | `hash.get(k)` |
226+
| `RETVAL` / `ST(0)` | `return new RuntimeScalar(x).getList()` |
227+
228+
### Using Existing Java Libraries
229+
230+
**Check `build.gradle` for available dependencies:**
231+
```bash
232+
grep "implementation" build.gradle
233+
```
234+
235+
**Common libraries already in PerlOnJava:**
236+
237+
| Java Library | Use Case | Example Module |
238+
|--------------|----------|----------------|
239+
| Gson | JSON parsing/encoding | `Json.java` |
240+
| jnr-posix | Native POSIX calls | `POSIX.java` |
241+
| jnr-ffi | Foreign function interface | Native bindings |
242+
| SnakeYAML | YAML parsing | `YAMLPP.java` |
243+
| TOML4J | TOML parsing | `Toml.java` |
244+
| Java stdlib | Crypto, encoding, time | Various |
245+
246+
**Example: JSON.java uses Gson directly:**
247+
```java
248+
import com.google.gson.Gson;
249+
import com.google.gson.GsonBuilder;
250+
251+
public static RuntimeList encode_json(RuntimeArray args, int ctx) {
252+
Gson gson = new GsonBuilder().create();
253+
String json = gson.toJson(convertToJava(args.get(0)));
254+
return new RuntimeScalar(json).getList();
255+
}
256+
```
257+
258+
**Standard Java imports:**
259+
```java
260+
// Time operations
261+
import java.time.*;
262+
import java.time.format.DateTimeFormatter;
263+
264+
// Crypto
265+
import java.security.MessageDigest;
266+
267+
// Encoding
268+
import java.util.Base64;
269+
import java.nio.charset.StandardCharsets;
270+
271+
// Native POSIX calls (with Windows fallbacks)
272+
import org.perlonjava.runtime.nativ.PosixLibrary;
273+
import org.perlonjava.runtime.nativ.NativeUtils;
274+
```
275+
276+
**Using PosixLibrary for native calls:**
277+
```java
278+
// Direct POSIX call (Unix only)
279+
int uid = PosixLibrary.INSTANCE.getuid();
280+
281+
// Cross-platform with Windows fallback (preferred)
282+
RuntimeScalar uid = NativeUtils.getuid(ctx);
283+
```
284+
285+
### Returning Different Types
286+
287+
```java
288+
// Scalar
289+
return new RuntimeScalar(value).getList();
290+
291+
// List
292+
RuntimeList result = new RuntimeList();
293+
result.add(new RuntimeScalar(item1));
294+
result.add(new RuntimeScalar(item2));
295+
return result;
296+
297+
// Array reference
298+
RuntimeArray arr = new RuntimeArray();
299+
arr.push(new RuntimeScalar(item));
300+
return arr.createReference().getList();
301+
302+
// Hash reference
303+
RuntimeHash hash = new RuntimeHash();
304+
hash.put("key", new RuntimeScalar(value));
305+
return hash.createReference().getList();
306+
```
307+
308+
### Handling Context
309+
310+
```java
311+
public static RuntimeList myMethod(RuntimeArray args, int ctx) {
312+
if (ctx == RuntimeContextType.SCALAR) {
313+
// Return single value
314+
return new RuntimeScalar(count).getList();
315+
} else {
316+
// Return list
317+
RuntimeList result = new RuntimeList();
318+
for (String item : items) {
319+
result.add(new RuntimeScalar(item));
320+
}
321+
return result;
322+
}
323+
}
324+
```
325+
326+
## Checklist
327+
328+
### Pre-porting
329+
- [ ] Fetch original `.pm` and `.xs` source
330+
- [ ] Study XS code to understand C algorithms and edge cases
331+
- [ ] Identify XS functions that need Java implementation
332+
- [ ] Check dependencies exist in PerlOnJava
333+
- [ ] Check `build.gradle`/`pom.xml` for usable Java libraries
334+
- [ ] Check `nativ/` package for POSIX functionality
335+
- [ ] Review existing similar modules for patterns
336+
337+
### Implementation
338+
- [ ] Create `ModuleName.java` with XS replacements
339+
- [ ] Create `Module/Name.pm` with pure Perl code
340+
- [ ] Add proper author/copyright attribution
341+
- [ ] Register all methods in `initialize()`
342+
343+
### Testing
344+
- [ ] Build compiles without errors: `./gradlew build -x test`
345+
- [ ] Basic functionality works: `./jperl -e 'use Module::Name; ...'`
346+
- [ ] Compare output with system Perl
347+
- [ ] Test edge cases identified in XS code
348+
349+
### Documentation
350+
- [ ] Add POD with AUTHOR and COPYRIGHT sections
351+
- [ ] Credit original authors
352+
353+
## Example: Time::Piece Port
354+
355+
**Files created:**
356+
- `src/main/java/org/perlonjava/runtime/perlmodule/TimePiece.java`
357+
- `src/main/java/org/perlonjava/runtime/perlmodule/POSIX.java` (for strftime)
358+
- `src/main/perl/lib/Time/Piece.pm`
359+
- `src/main/perl/lib/Time/Seconds.pm`
360+
361+
**XS functions replaced:**
362+
| XS Function | Java Implementation |
363+
|-------------|---------------------|
364+
| `_strftime(fmt, epoch, islocal)` | `DateTimeFormatter` with format mapping |
365+
| `_strptime(str, fmt, gmt, locale)` | `DateTimeFormatter.parse()` |
366+
| `_tzset()` | No-op (Java handles TZ) |
367+
| `_crt_localtime(epoch)` | `ZonedDateTime` conversion |
368+
| `_crt_gmtime(epoch)` | `ZonedDateTime` at UTC |
369+
| `_get_localization()` | `DateFormatSymbols` |
370+
| `_mini_mktime(...)` | `LocalDateTime` normalization |
371+
372+
**Pure Perl reused (~80%):**
373+
- All accessor methods (sec, min, hour, year, etc.)
374+
- Formatting helpers (ymd, hms, datetime)
375+
- Julian day calculations
376+
- Overloaded operators
377+
- Import/export logic
378+
379+
## Troubleshooting
380+
381+
### "Can't load Java XS module"
382+
- Check class name matches: `Module::Name` → `ModuleName.java`
383+
- Verify `initialize()` method exists and is static
384+
- Check package is `org.perlonjava.runtime.perlmodule`
385+
386+
### Method not found
387+
- Ensure method is registered in `initialize()`
388+
- Check method signature: `public static RuntimeList name(RuntimeArray args, int ctx)`
389+
390+
### Different output than system Perl
391+
- Compare with fixed test values (not current time)
392+
- Check locale handling
393+
- Verify edge cases from XS comments
394+
395+
## References
396+
397+
- Module porting guide: `docs/guides/module-porting.md`
398+
- Existing modules: `src/main/java/org/perlonjava/runtime/perlmodule/`
399+
- Runtime types: `src/main/java/org/perlonjava/runtime/runtimetypes/`

0 commit comments

Comments
 (0)