Skip to content

Commit f027bf4

Browse files
committed
fix sprintf WIP
1 parent 1743419 commit f027bf4

File tree

1 file changed

+175
-11
lines changed

1 file changed

+175
-11
lines changed

src/main/java/org/perlonjava/operators/SprintfOperator.java

Lines changed: 175 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@ public static RuntimeScalar sprintf(RuntimeScalar runtimeScalar, RuntimeList lis
4141
String format = runtimeScalar.toString();
4242

4343
StringBuilder result = new StringBuilder();
44-
int argIndex = 0;
44+
int argIndex = 0; // Sequential argument index
45+
int maxArgIndexUsed = -1; // Track highest argument index used
46+
boolean hasValidSpecifier = false; // Track if we have any valid specifiers
47+
boolean hasPositionalParameter = false; // Track if any positional parameters are used
48+
boolean hasInvalidSpecifier = false; // Track if any invalid specifiers were found
4549

4650
// Parse the format string into literals and format specifiers
4751
SprintfFormatParser.ParseResult parsed = SprintfFormatParser.parse(format);
@@ -60,6 +64,7 @@ public static RuntimeScalar sprintf(RuntimeScalar runtimeScalar, RuntimeList lis
6064
if (!spec.isValid) {
6165
// Just generate the warning, don't add to result
6266
handleInvalidSpecifier(spec);
67+
hasInvalidSpecifier = true;
6368
}
6469
continue; // Skip adding to result and updating argIndex
6570
}
@@ -69,38 +74,87 @@ public static RuntimeScalar sprintf(RuntimeScalar runtimeScalar, RuntimeList lis
6974
String formatted = processFormatSpecifier(spec, list, argIndex, formatter);
7075
result.append(formatted);
7176
charsWritten += formatted.length();
77+
hasInvalidSpecifier = true;
78+
// Invalid specifiers don't consume arguments
7279
continue;
7380
}
7481

82+
// Check for positional parameters
83+
if (spec.parameterIndex != null || spec.widthArgIndex != null || spec.precisionArgIndex != null) {
84+
hasPositionalParameter = true;
85+
}
86+
87+
// We have a valid specifier
88+
hasValidSpecifier = true;
89+
7590
if (spec.conversionChar == 'n') {
7691
// %n doesn't produce output, but does consume an argument
92+
int targetIndex = spec.parameterIndex != null ? spec.parameterIndex - 1 : argIndex;
93+
maxArgIndexUsed = Math.max(maxArgIndexUsed, targetIndex);
7794
handlePercentN(spec, list, argIndex);
7895

79-
// Update argument index
96+
// Update sequential argument index for non-positional width/precision
8097
if (spec.parameterIndex == null) {
8198
argIndex++; // %n does consume an argument
82-
if (spec.widthFromArg && spec.widthArgIndex == null) argIndex++;
83-
if (spec.precisionFromArg && spec.precisionArgIndex == null) argIndex++;
99+
}
100+
if (spec.widthFromArg && spec.widthArgIndex == null) {
101+
argIndex++;
102+
}
103+
if (spec.precisionFromArg && spec.precisionArgIndex == null) {
104+
argIndex++;
84105
}
85106

86107
// Don't add anything to result or charsWritten
87108
continue; // Skip to next format element
88109
} else {
89-
String formatted = processFormatSpecifier(spec, list, argIndex, formatter);
90-
result.append(formatted);
91-
charsWritten += formatted.length();
110+
ProcessResult processResult = processFormatSpecifierTracked(spec, list, argIndex, formatter);
111+
result.append(processResult.formatted);
112+
charsWritten += processResult.formatted.length();
92113

93-
// Update argument index if not using positional parameters
94-
if (spec.parameterIndex == null && spec.conversionChar != '%') {
95-
argIndex = updateArgIndex(spec, argIndex);
114+
// Only update maxArgIndexUsed if this specifier actually consumed arguments
115+
if (spec.conversionChar != '%' || spec.widthFromArg) {
116+
maxArgIndexUsed = Math.max(maxArgIndexUsed, processResult.maxArgIndexUsed);
117+
}
118+
119+
// Update sequential argument index based on what was consumed
120+
if (spec.parameterIndex == null) {
121+
// Non-positional value argument advances the sequential index
122+
argIndex++;
123+
}
124+
// Width/precision without explicit position also advance sequential index
125+
if (spec.widthFromArg && spec.widthArgIndex == null) {
126+
argIndex++;
127+
}
128+
if (spec.precisionFromArg && spec.precisionArgIndex == null) {
129+
argIndex++;
96130
}
97131
}
98132
}
99133
}
100134

135+
// Only check for redundant arguments if:
136+
// 1. We had at least one valid specifier that consumed arguments
137+
// 2. No positional parameters were used
138+
// 3. No invalid specifiers were found
139+
// 4. There are unused arguments
140+
if (hasValidSpecifier && !hasPositionalParameter && !hasInvalidSpecifier &&
141+
maxArgIndexUsed >= 0 && maxArgIndexUsed + 1 < list.size()) {
142+
WarnDie.warn(new RuntimeScalar("Redundant argument in sprintf"), new RuntimeScalar(""));
143+
}
144+
101145
return new RuntimeScalar(result.toString());
102146
}
103147

148+
private static class ProcessResult {
149+
String formatted;
150+
int maxArgIndexUsed;
151+
152+
ProcessResult(String formatted, int maxArgIndexUsed) {
153+
this.formatted = formatted;
154+
this.maxArgIndexUsed = maxArgIndexUsed;
155+
}
156+
}
157+
104158
private static void handlePercentN(FormatSpecifier spec,
105159
RuntimeList list, int argIndex) {
106160
int targetIndex = spec.parameterIndex != null ? spec.parameterIndex - 1 : argIndex;
@@ -111,20 +165,119 @@ private static void handlePercentN(FormatSpecifier spec,
111165
}
112166
}
113167

168+
/**
169+
* Process a single format specifier with tracking.
170+
*/
171+
private static ProcessResult processFormatSpecifierTracked(
172+
FormatSpecifier spec,
173+
RuntimeList list,
174+
int sepArgIndex,
175+
SprintfValueFormatter formatter) {
176+
177+
String formatted = processFormatSpecifier(spec, list, sepArgIndex, formatter, -1);
178+
179+
// Calculate max arg index used
180+
int maxUsed = -1;
181+
182+
// Handle %% - literal percent sign
183+
if (spec.conversionChar == '%') {
184+
if (spec.widthFromArg) {
185+
// %*% consumes a width argument
186+
if (spec.widthArgIndex != null) {
187+
maxUsed = Math.max(maxUsed, spec.widthArgIndex - 1);
188+
} else if (spec.parameterIndex == null) {
189+
maxUsed = Math.max(maxUsed, sepArgIndex);
190+
}
191+
}
192+
return new ProcessResult(formatted, maxUsed);
193+
}
194+
195+
// For invalid specifiers, don't track any arguments
196+
if (!spec.isValid) {
197+
return new ProcessResult(formatted, maxUsed);
198+
}
199+
200+
// Special handling for %*v formats
201+
if (spec.vectorFlag && spec.widthFromArg && spec.raw.matches(".*\\*v.*")) {
202+
// %*v format - first arg is separator
203+
int currentIndex = sepArgIndex;
204+
205+
// Track separator argument
206+
maxUsed = Math.max(maxUsed, currentIndex);
207+
currentIndex++;
208+
209+
if (spec.precisionFromArg) {
210+
// %*v*d format - second arg is width
211+
maxUsed = Math.max(maxUsed, currentIndex);
212+
currentIndex++;
213+
}
214+
215+
// Track value argument
216+
maxUsed = Math.max(maxUsed, currentIndex);
217+
218+
return new ProcessResult(formatted, maxUsed);
219+
}
220+
221+
// Track all consumed arguments properly
222+
FormatArguments args = extractFormatArguments(spec, list, sepArgIndex);
223+
224+
// Track width argument if consumed
225+
if (spec.widthFromArg) {
226+
if (spec.widthArgIndex != null) {
227+
maxUsed = Math.max(maxUsed, spec.widthArgIndex - 1);
228+
} else {
229+
// Width from sequential position
230+
int widthPos = sepArgIndex;
231+
if (spec.parameterIndex != null) {
232+
// For %2$*d, width comes from current sequential position
233+
widthPos = sepArgIndex;
234+
}
235+
maxUsed = Math.max(maxUsed, widthPos);
236+
}
237+
}
238+
239+
// Track precision argument if consumed
240+
if (spec.precisionFromArg) {
241+
if (spec.precisionArgIndex != null) {
242+
maxUsed = Math.max(maxUsed, spec.precisionArgIndex - 1);
243+
} else {
244+
// Precision from sequential position
245+
int precPos = sepArgIndex;
246+
if (spec.widthFromArg && spec.widthArgIndex == null) {
247+
precPos++; // After width
248+
}
249+
if (spec.parameterIndex != null && !spec.widthFromArg) {
250+
// For %2$.*d, precision comes from current sequential position
251+
precPos = sepArgIndex;
252+
}
253+
maxUsed = Math.max(maxUsed, precPos);
254+
}
255+
}
256+
257+
// Track value argument
258+
if (args.valueArgIndex < list.size()) {
259+
maxUsed = Math.max(maxUsed, args.valueArgIndex);
260+
}
261+
262+
return new ProcessResult(formatted, maxUsed);
263+
}
264+
114265
/**
115266
* Process a single format specifier.
116267
*
117268
* @param spec The parsed format specifier
118269
* @param list The argument list
119270
* @param sepArgIndex Current argument index
120271
* @param formatter The value formatter instance
272+
* @param maxArgIndexUsed Maximum argument index used so far (for tracking)
121273
* @return The formatted string
122274
*/
123275
private static String processFormatSpecifier(
124276
FormatSpecifier spec,
125277
RuntimeList list,
126278
int sepArgIndex,
127-
SprintfValueFormatter formatter) {
279+
SprintfValueFormatter formatter,
280+
int maxArgIndexUsed) {
128281

129282
// System.err.println("DEBUG processFormatSpecifier: raw='" + spec.raw + "', isValid=" + spec.isValid + ", errorMessage=" + spec.errorMessage);
130283

@@ -209,6 +362,17 @@ private static String processFormatSpecifier(
209362
}
210363
}
211364

365+
/**
366+
* Process without tracking (for compatibility).
367+
*/
368+
private static String processFormatSpecifier(
369+
FormatSpecifier spec,
370+
RuntimeList list,
371+
int sepArgIndex,
372+
SprintfValueFormatter formatter) {
373+
return processFormatSpecifier(spec, list, sepArgIndex, formatter, -1);
374+
}
375+
212376
/**
213377
* Extract width, precision, and value index from the format specifier.
214378
*/

0 commit comments

Comments
 (0)