@@ -169,10 +169,34 @@ public static RuntimeScalar pack(RuntimeList args) {
169169 continue ;
170170 }
171171
172- // Check if this is a numeric format followed by '/' - skip it entirely
173- if (isNumericFormat (format ) && i + 1 < template .length () && template .charAt (i + 1 ) == '/' ) {
174- // System.err.println("DEBUG: skipping format '" + format + "' because it's followed by '/', valueIndex=" + valueIndex);
175- continue ;
172+ // Check if this is a numeric/Z format followed by '/' - skip it entirely
173+ if ((isNumericFormat (format ) || format == 'Z' ) && i + 1 < template .length ()) {
174+ // Look ahead, skipping modifiers and counts
175+ int lookAhead = i + 1 ;
176+
177+ // Skip modifiers
178+ while (lookAhead < template .length () &&
179+ (template .charAt (lookAhead ) == '<' ||
180+ template .charAt (lookAhead ) == '>' ||
181+ template .charAt (lookAhead ) == '!' )) {
182+ lookAhead ++;
183+ }
184+
185+ // Skip count or *
186+ if (lookAhead < template .length () && template .charAt (lookAhead ) == '*' ) {
187+ lookAhead ++;
188+ } else if (lookAhead < template .length () && Character .isDigit (template .charAt (lookAhead ))) {
189+ while (lookAhead < template .length () && Character .isDigit (template .charAt (lookAhead ))) {
190+ lookAhead ++;
191+ }
192+ }
193+
194+ // Check if followed by '/'
195+ if (lookAhead < template .length () && template .charAt (lookAhead ) == '/' ) {
196+ // Skip this entire format sequence - it's used for length encoding
197+ i = lookAhead - 1 ; // -1 because loop will increment
198+ continue ;
199+ }
176200 }
177201
178202 // Parse modifiers BEFORE parsing counts
@@ -257,22 +281,60 @@ public static RuntimeScalar pack(RuntimeList args) {
257281 }
258282 } else if (format == '/' ) {
259283 // System.err.println("DEBUG: entering '/' handler, valueIndex=" + valueIndex);
260- // '/' must follow a numeric type
284+ // '/' must follow a numeric type or Z
261285 if (i == 0 ) {
262286 throw new PerlCompilerException ("Invalid type '/'" );
263287 }
264288
265- // Find the numeric format that precedes '/'
266- // Need to look back, skipping any modifiers
289+ // Find the format that precedes '/'
290+ // Need to look back, skipping any modifiers and repeat counts
267291 int numericPos = i - 1 ;
292+
293+ // Skip back over repeat counts and '*'
294+ if (numericPos > 0 && (template .charAt (numericPos ) == '*' || Character .isDigit (template .charAt (numericPos )))) {
295+ if (template .charAt (numericPos ) == '*' ) {
296+ // Skip the '*' to get to the format
297+ numericPos --;
298+ } else {
299+ // Skip digits
300+ while (numericPos > 0 && Character .isDigit (template .charAt (numericPos ))) {
301+ numericPos --;
302+ }
303+ // Now numericPos points to the format character
304+ }
305+ }
306+
307+ // Skip back over any modifiers
268308 while (numericPos > 0 && (template .charAt (numericPos ) == '<' ||
269309 template .charAt (numericPos ) == '>' ||
270310 template .charAt (numericPos ) == '!' )) {
271311 numericPos --;
272312 }
273313
274- char lengthFormat = template .charAt (numericPos );
275- if (!isNumericFormat (lengthFormat )) {
314+ // If we hit a ')', we need to look at what's inside the group
315+ char lengthFormat ;
316+ if (numericPos >= 0 && template .charAt (numericPos ) == ')' ) {
317+ // Find the matching '(' and get the last numeric format in the group
318+ int openPos = numericPos - 1 ;
319+ int depth = 1 ;
320+ while (openPos >= 0 && depth > 0 ) {
321+ if (template .charAt (openPos ) == ')' ) depth ++;
322+ else if (template .charAt (openPos ) == '(' ) depth --;
323+ openPos --;
324+ }
325+ openPos ++; // Move back to the '('
326+
327+ // Find the last numeric format in the group
328+ lengthFormat = findLastNumericFormat (template .substring (openPos + 1 , numericPos ));
329+ if (lengthFormat == '\0' ) {
330+ throw new PerlCompilerException ("'/' must follow a numeric type" );
331+ }
332+ } else if (numericPos >= 0 ) {
333+ lengthFormat = template .charAt (numericPos );
334+ if (!isNumericFormat (lengthFormat ) && lengthFormat != 'Z' ) {
335+ throw new PerlCompilerException ("'/' must follow a numeric type" );
336+ }
337+ } else {
276338 throw new PerlCompilerException ("'/' must follow a numeric type" );
277339 }
278340
@@ -355,8 +417,15 @@ public static RuntimeScalar pack(RuntimeList args) {
355417 output .write (lengthToWrite & 0xFF );
356418 break ;
357419 case 'Z' :
358- output .write (lengthToWrite & 0xFF );
359- break ;
420+ // For Z*/, encode length as null-terminated decimal string
421+ String lengthStr = String .valueOf (lengthToWrite );
422+ try {
423+ output .write (lengthStr .getBytes (StandardCharsets .US_ASCII ));
424+ output .write (0 ); // null terminator
425+ } catch (IOException e ) {
426+ throw new RuntimeException (e );
427+ }
428+ break ;
360429 default :
361430 throw new PerlCompilerException ("Invalid length type '" + lengthFormat + "' for '/'" );
362431 }
@@ -660,7 +729,7 @@ private static boolean hasConflictingEndianness(String groupContent, char groupE
660729 char modifier = groupContent .charAt (checkPos );
661730 if (modifier == '<' || modifier == '>' ) {
662731 if ((modifier == '<' && groupEndian == '>' ) ||
663- (modifier == '>' && groupEndian == '<' )) {
732+ (modifier == '>' && groupEndian == '<' )) {
664733 return true ;
665734 }
666735 break ;
@@ -710,9 +779,9 @@ private static int countValuesNeeded(String template) {
710779 i = closePos ;
711780 // Skip modifiers
712781 while (i + 1 < template .length () &&
713- (template .charAt (i + 1 ) == '<' ||
714- template .charAt (i + 1 ) == '>' ||
715- template .charAt (i + 1 ) == '!' )) {
782+ (template .charAt (i + 1 ) == '<' ||
783+ template .charAt (i + 1 ) == '>' ||
784+ template .charAt (i + 1 ) == '!' )) {
716785 i ++;
717786 }
718787
@@ -744,9 +813,9 @@ private static int countValuesNeeded(String template) {
744813
745814 // Skip modifiers
746815 while (i + 1 < template .length () &&
747- (template .charAt (i + 1 ) == '<' ||
748- template .charAt (i + 1 ) == '>' ||
749- template .charAt (i + 1 ) == '!' )) {
816+ (template .charAt (i + 1 ) == '<' ||
817+ template .charAt (i + 1 ) == '>' ||
818+ template .charAt (i + 1 ) == '!' )) {
750819 i ++;
751820 }
752821
@@ -777,4 +846,26 @@ private static int countValuesNeeded(String template) {
777846 }
778847 return count ;
779848 }
780- }
849+
850+ private static char findLastNumericFormat (String template ) {
851+ char lastNumeric = '\0' ;
852+ for (int i = 0 ; i < template .length (); i ++) {
853+ char c = template .charAt (i );
854+ if (isNumericFormat (c ) || c == 'Z' ) {
855+ lastNumeric = c ;
856+ } else if (c == '(' ) {
857+ // Skip the group
858+ int closePos = findMatchingParen (template , i );
859+ if (closePos != -1 ) {
860+ // Check inside the group
861+ char groupNumeric = findLastNumericFormat (template .substring (i + 1 , closePos ));
862+ if (groupNumeric != '\0' ) {
863+ lastNumeric = groupNumeric ;
864+ }
865+ i = closePos ;
866+ }
867+ }
868+ }
869+ return lastNumeric ;
870+ }
871+ }
0 commit comments