JPC-RR r11.7
[jpcrr.git] / mnj / lua / FormatItem.java
blob7edb18d37e457f4f89b51e059de42beb6550017b
1 /* $Header: //info.ravenbrook.com/project/jili/version/1.1/code/mnj/lua/StringLib.java#1 $
2 * Copyright (c) 2006 Nokia Corporation and/or its subsidiary(-ies).
3 * All rights reserved.
5 * Permission is hereby granted, free of charge, to any person obtaining
6 * a copy of this software and associated documentation files (the
7 * "Software"), to deal in the Software without restriction, including
8 * without limitation the rights to use, copy, modify, merge, publish,
9 * distribute, sublicense, and/or sell copies of the Software, and to
10 * permit persons to whom the Software is furnished to do so, subject
11 * to the following conditions:
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
20 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
21 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 // Modified 2009-12-26 by Ilari Liusvaara
26 // Split -> StringLib -> StringLib, MatchState and FormatItem as J2SE
27 // doesn't like multiple classes in the same file.
29 package mnj.lua;
31 final class FormatItem
33 private Lua L;
34 private boolean left; // '-' flag
35 private boolean sign; // '+' flag
36 private boolean space; // ' ' flag
37 private boolean alt; // '#' flag
38 private boolean zero; // '0' flag
39 private int width; // minimum field width
40 private int precision = -1; // precision, -1 when no precision specified.
41 private char type; // the type of the conversion
42 private int length; // length of the format item in the format string.
44 /**
45 * Character used in formatted output when %e or %g format is used.
47 static char E_LOWER = 'E';
48 /**
49 * Character used in formatted output when %E or %G format is used.
51 static char E_UPPER = 'E';
53 /**
54 * Parse a format item (starting from after the <code>L_ESC</code>).
55 * If you promise that there won't be any format errors, then
56 * <var>L</var> can be <code>null</code>.
58 FormatItem(Lua L, String s)
60 this.L = L;
61 int i=0;
62 int l = s.length();
63 // parse flags
64 flag:
65 while (true)
67 if (i >=l )
68 L.error("invalid format");
69 switch (s.charAt(i))
71 case '-':
72 left = true;
73 break;
74 case '+':
75 sign = true;
76 break;
77 case ' ':
78 space = true;
79 break;
80 case '#':
81 alt = true;
82 break;
83 case '0':
84 zero = true;
85 break;
86 default:
87 break flag;
89 ++i;
90 } /* flag */
91 // parse width
92 int widths = i; // index of start of width specifier
93 while (true)
95 if (i >= l)
96 L.error("invalid format");
97 if (Syntax.isdigit(s.charAt(i)))
98 ++i;
99 else
100 break;
102 if (widths < i)
106 width = Integer.parseInt(s.substring(widths, i));
108 catch (NumberFormatException e_)
112 // parse precision
113 if (s.charAt(i) == '.')
115 ++i;
116 int precisions = i; // index of start of precision specifier
117 while (true)
119 if (i >= l)
120 L.error("invalid format");
121 if (Syntax.isdigit(s.charAt(i)))
122 ++i;
123 else
124 break;
126 if (precisions < i)
130 precision = Integer.parseInt(s.substring(precisions, i));
132 catch (NumberFormatException e_)
137 switch (s.charAt(i))
139 case 'c':
140 case 'd': case 'i':
141 case 'o': case 'u': case 'x': case 'X':
142 case 'e': case 'E': case 'f': case 'g': case 'G':
143 case 'q':
144 case 's':
145 type = s.charAt(i);
146 length = i+1;
147 return;
149 L.error("invalid option to 'format'");
152 int length()
154 return length;
157 int type()
159 return type;
163 * Format the converted string according to width, and left.
164 * zero padding is handled in either {@link FormatItem#formatInteger}
165 * or {@link FormatItem#formatFloat}
166 * (and width is fixed to 0 in such cases). Therefore we can ignore
167 * zero.
169 private void format(StringBuffer b, String s)
171 int l = s.length();
172 if (l >= width)
174 b.append(s);
175 return;
177 StringBuffer pad = new StringBuffer();
178 while (l < width)
180 pad.append(' ');
181 ++l;
183 if (left)
185 b.append(s);
186 b.append(pad);
188 else
190 b.append(pad);
191 b.append(s);
195 // All the format* methods take a StringBuffer and append the
196 // formatted representation of the value to it.
197 // Sadly after a format* method has been invoked the object is left in
198 // an unusable state and should not be used again.
200 void formatChar(StringBuffer b, char c)
202 String s = String.valueOf(c);
203 format(b, s);
206 void formatInteger(StringBuffer b, long i)
208 // :todo: improve inefficient use of implicit StringBuffer
210 if (left)
211 zero = false;
212 if (precision >= 0)
213 zero = false;
215 int radix = 10;
216 switch (type)
218 case 'o':
219 radix = 8;
220 break;
221 case 'd': case 'i': case 'u':
222 radix = 10;
223 break;
224 case 'x': case 'X':
225 radix = 16;
226 break;
227 default:
228 L.error("invalid format");
230 String s = Long.toString(i, radix);
231 if (type == 'X')
232 s = s.toUpperCase();
233 if (precision == 0 && s.equals("0"))
234 s = "";
236 // form a prefix by strippping possible leading '-',
237 // pad to precision,
238 // add prefix,
239 // pad to width.
240 // extra wart: padding with '0' is implemented using precision
241 // because this makes handling the prefix easier.
242 String prefix = "";
243 if (s.startsWith("-"))
245 prefix = "-";
246 s = s.substring(1);
248 if (alt && radix == 16)
249 prefix = "0x";
250 if (prefix == "")
252 if (sign)
253 prefix = "+";
254 else if (space)
255 prefix = " ";
257 if (alt && radix == 8 && !s.startsWith("0"))
258 s = "0" + s;
259 int l = s.length();
260 if (zero)
262 precision = width - prefix.length();
263 width = 0;
265 if (l < precision)
267 StringBuffer p = new StringBuffer();
268 while (l < precision)
270 p.append('0');
271 ++l;
273 p.append(s);
274 s = p.toString();
276 s = prefix + s;
277 format(b, s);
280 void formatFloat(StringBuffer b, double d)
282 switch (type)
284 case 'g': case 'G':
285 formatFloatG(b, d);
286 return;
287 case 'f':
288 formatFloatF(b, d);
289 return;
290 case 'e': case 'E':
291 formatFloatE(b, d);
292 return;
296 private void formatFloatE(StringBuffer b, double d)
298 String s = formatFloatRawE(d);
299 format(b, s);
303 * Returns the formatted string for the number without any padding
304 * (which can be added by invoking {@link FormatItem#format} later).
306 private String formatFloatRawE(double d)
308 double m = Math.abs(d);
309 int offset = 0;
310 if (m >= 1e-3 && m < 1e7)
312 d *= 1e10;
313 offset = 10;
316 String s = Double.toString(d);
317 StringBuffer t = new StringBuffer(s);
318 int e; // Exponent value
319 if (d == 0)
321 e = 0;
323 else
325 int ei = s.indexOf('E');
326 e = Integer.parseInt(s.substring(ei+1));
327 t.delete(ei, Integer.MAX_VALUE);
330 precisionTrim(t);
332 e -= offset;
333 if (Character.isLowerCase(type))
335 t.append(E_LOWER);
337 else
339 t.append(E_UPPER);
341 if (e >= 0)
343 t.append('+');
345 t.append(Integer.toString(e));
347 zeroPad(t);
348 return t.toString();
351 private void formatFloatF(StringBuffer b, double d)
353 String s = formatFloatRawF(d);
354 format(b, s);
358 * Returns the formatted string for the number without any padding
359 * (which can be added by invoking {@link FormatItem#format} later).
361 private String formatFloatRawF(double d)
363 String s = Double.toString(d);
364 StringBuffer t = new StringBuffer(s);
366 int di = s.indexOf('.');
367 int ei = s.indexOf('E');
368 if (ei >= 0)
370 t.delete(ei, Integer.MAX_VALUE);
371 int e = Integer.parseInt(s.substring(ei+1));
373 StringBuffer z = new StringBuffer();
374 for (int i=0; i<Math.abs(e); ++i)
376 z.append('0');
379 if (e > 0)
381 t.deleteCharAt(di);
382 t.append(z);
383 t.insert(di+e, '.');
385 else
387 t.deleteCharAt(di);
388 int at = t.charAt(0) == '-' ? 1 : 0;
389 t.insert(at, z);
390 t.insert(di, '.');
394 precisionTrim(t);
395 zeroPad(t);
397 return t.toString();
400 private void formatFloatG(StringBuffer b, double d)
402 if (precision == 0)
404 precision = 1;
406 if (precision < 0)
408 precision = 6;
410 String s;
411 // Decide whether to use %e or %f style.
412 double m = Math.abs(d);
413 if (m == 0)
415 // :todo: Could test for -0 and use "-0" appropriately.
416 s = "0";
418 else if (m < 1e-4 || m >= Lua.iNumpow(10, precision))
420 // %e style
421 --precision;
422 s = formatFloatRawE(d);
423 int di = s.indexOf('.');
424 if (di >= 0)
426 // Trim trailing zeroes from fractional part
427 int ei = s.indexOf('E');
428 if (ei < 0)
430 ei = s.indexOf('e');
432 int i = ei-1;
433 while (s.charAt(i) == '0')
435 --i;
437 if (s.charAt(i) != '.')
439 ++i;
441 StringBuffer a = new StringBuffer(s);
442 a.delete(i, ei);
443 s = a.toString();
446 else
448 // %f style
449 // For %g precision specifies the number of significant digits,
450 // for %f precision specifies the number of fractional digits.
451 // There is a problem because it's not obvious how many fractional
452 // digits to format, it could be more than precision
453 // (when .0001 <= m < 1) or it could be less than precision
454 // (when m >= 1).
455 // Instead of trying to work out the correct precision to use for
456 // %f formatting we use a worse case to get at least all the
457 // necessary digits, then we trim using string editing. The worst
458 // case is that 3 zeroes come after the decimal point before there
459 // are any significant digits.
460 // Save the required number of significant digits
461 int required = precision;
462 precision += 3;
463 s = formatFloatRawF(d);
464 int fsd = 0; // First Significant Digit
465 while (s.charAt(fsd) == '0' || s.charAt(fsd) == '.')
467 ++fsd;
469 // Note that all the digits to the left of the decimal point in
470 // the formatted number are required digits (either significant
471 // when m >= 1 or 0 when m < 1). We know this because otherwise
472 // m >= (10**precision) and so formatting falls under the %e case.
473 // That means that we can always trim the string at fsd+required
474 // (this will remove the decimal point when m >=
475 // (10**(precision-1)).
476 StringBuffer a = new StringBuffer(s);
477 a.delete(fsd+required, Integer.MAX_VALUE);
478 if (s.indexOf('.') < a.length())
480 // Trim trailing zeroes
481 int i = a.length() - 1;
482 while (a.charAt(i) == '0')
484 a.deleteCharAt(i);
485 --i;
487 if (a.charAt(i) == '.')
489 a.deleteCharAt(i);
492 s = a.toString();
494 format(b, s);
497 void formatString(StringBuffer b, String s)
499 String p = s;
501 if (precision >= 0 && precision < s.length())
503 p = s.substring(0, precision);
505 format(b, p);
508 private void precisionTrim(StringBuffer t)
510 if (precision < 0)
512 precision = 6;
515 String s = t.toString();
516 int di = s.indexOf('.');
517 int l = t.length();
518 if (0 == precision)
520 t.delete(di, Integer.MAX_VALUE);
522 else if (l > di+precision)
524 t.delete(di+precision+1, Integer.MAX_VALUE);
526 else
528 for(; l <= di+precision; ++l)
530 t.append('0');
535 private void zeroPad(StringBuffer t)
537 if (zero && t.length() < width)
539 int at = t.charAt(0) == '-' ? 1 : 0;
540 while (t.length() < width)
542 t.insert(at, '0');