| package xdc.rta; |
| |
| import java.io.IOException; |
| import xdc.rov.ISymbolTable; |
| |
| /* |
| * ======== Formatter ======== |
| * Class for formatting printf strings. |
| * |
| * This class intended to be used for decoding printfs from xdc.runtime.Log |
| * records. An important assumption is that all of the printf arguments have |
| * been cast to IArgs on the target. |
| * |
| * For targets with 32-bit IArgs but 16-bit integers, for example, this means |
| * that the 16-bit values must be cast to 32-bits (handling sign extension) |
| * before passing them to the Formatter. |
| */ |
| public class Formatter { |
| |
| private static ISymbolTable symTab = null; |
| private static IOFReader ofReader = null; |
| private static int argSize = 32; |
| |
| public static void setSymbolTable(ISymbolTable inSymTab) |
| { |
| symTab = inSymTab; |
| } |
| |
| public static void setOFReader(IOFReader inOFReader) |
| { |
| ofReader = inOFReader; |
| } |
| |
| /* |
| * ======== setArgSize ======== |
| * Set the size of the target Arg type in bits. |
| * |
| * This is used when looking up strings on the target. The arguments |
| * are assumed to be signed values, which need to be converted to |
| * unsigned in order to treat them as addresses. |
| */ |
| public static void setArgSize(int inArgSize) |
| { |
| argSize = inArgSize; |
| } |
| |
| public static ISymbolTable getSymbolTable() |
| { |
| return (symTab); |
| } |
| |
| /* |
| * ======== ParseData ======== |
| */ |
| static class ParseData { |
| /* The string to be added to the result. */ |
| public String str; |
| |
| /* |
| * The number of characters of the string to use. |
| * Maintaining this number minimizes the amount of string |
| * manipulation that needs to be done--instead of removing |
| * characters from 'str', less characters are added to the |
| * result. |
| */ |
| public int len; |
| public boolean lJust; |
| public boolean lFlag; |
| public int zpad; |
| public int width; |
| public int precis; |
| } |
| |
| /* |
| * ======== doPrint ======== |
| * Performs printf style formatting. |
| * |
| * This API does not require the argument size, it assumes a default |
| * size of 4. |
| */ |
| public static String doPrint(String format, int [] args) |
| { |
| return (doPrintRecur(format, args, 4).result); |
| } |
| |
| /* |
| * ======== doPrint ======== |
| * Peforms printf style formatting. |
| * |
| * This API requires the argument size. |
| */ |
| public static String doPrint(String format, int [] args, int argSize) |
| { |
| return (doPrintRecur(format, args, argSize).result); |
| } |
| |
| /* |
| * ======== Result ======== |
| * Structure to hold the result of a recursive doPrint, including |
| * the formatted string and the number of arguments consumed. |
| */ |
| public static class Result { |
| String result = ""; |
| int numArgsConsumed = 0; |
| } |
| |
| /* |
| * ======== doPrintRecur ======== |
| * Perform printf formatting with support for recursive format strings |
| * |
| * format - The string to be formatted |
| * args - Array of arguments to the format string |
| * argSize - The size of the target type 'Arg' in bytes. This is used to |
| * determine the number of digits to use when converting an arg |
| * to its hexadecimal representation, as well as for correctly |
| * upcasting the arguments to 64-bit longs (handling sign |
| * extension). The arguments must be upcast to 64-bit longs |
| * because there is no unsigned int type in Java. |
| * |
| * The return value includes the formatted string and the number of |
| * arguments consumed by this call to doPrint. This supports recursive |
| * formatting for the %$S specifier. |
| */ |
| private static Result doPrintRecur(String format, int [] args, int argSize) |
| { |
| Result res = new Result(); |
| |
| /* Check for null format string */ |
| if (format == null) { |
| res.result = "(null format)"; |
| return (res); |
| } |
| |
| /* |
| * Convert the format string to a char array so we can treat it one |
| * character at a time. |
| */ |
| char [] fmt = format.toCharArray(); |
| |
| /* The current character we're looking at. */ |
| char c; |
| |
| /* Running index into format string */ |
| int findex = 0; |
| |
| /* Running index into args array */ |
| int aindex = 0; |
| |
| /* Data structure to hold meta data for a single format specifier. */ |
| ParseData parse = new ParseData(); |
| |
| /* |
| * Look at each character in the string. |
| * |
| * When a format specifier is encountered, this code will deal with all |
| * of the characters in the specifier before doing another iteration of |
| * the while loop. |
| */ |
| while (findex < fmt.length) { |
| |
| /* Look at the next character. */ |
| c = fmt[findex++]; |
| |
| /* |
| * If it's not a format specifier, just add it to the result |
| * and move on to the next character. |
| */ |
| if (c != '%') { |
| res.result += Character.toString(c); |
| continue; |
| } |
| |
| /* |
| * We've found a format specifier. Get the next character in the |
| * specifier. |
| */ |
| c = fmt[findex++]; |
| |
| /* check for - flag (pad on right) */ |
| if (c == '-') { |
| parse.lJust = true; |
| c = fmt[findex++]; |
| } |
| else { |
| parse.lJust = false; |
| } |
| |
| /* check for leading 0 pad */ |
| if (c == '0') { |
| parse.zpad = 1; |
| c = fmt[findex++]; |
| } |
| else { |
| parse.zpad = 0; |
| } |
| |
| /* allow optional field width/precision specification */ |
| parse.width = 0; |
| parse.precis = -1; |
| |
| if (c == '*') { |
| parse.width = aindex >= args.length ? 0 : args[aindex++]; |
| c = fmt[findex++]; |
| if (parse.width < 0) { |
| parse.lJust = true; |
| parse.width = -parse.width; |
| } |
| } |
| else { |
| while (c >= '0' && c <= '9') { |
| parse.width = parse.width * 10 + c - '0'; |
| c = fmt[findex++]; |
| } |
| } |
| |
| /* allow optional field precision specification */ |
| if (c == '.') { |
| parse.precis = 0; |
| c = fmt[findex++]; |
| if (c == '*') { |
| parse.precis = aindex >= args.length ? 0 : args[aindex++]; |
| if (parse.precis < 0) { |
| parse.precis = 0; |
| } |
| |
| c = fmt[findex++]; |
| } |
| else { |
| while (c >= '0' && c <= '9') { |
| parse.precis = parse.precis * 10 + c - '0'; |
| c = fmt[findex++]; |
| } |
| } |
| } |
| |
| /* setup for leading zero padding */ |
| if (parse.zpad != 0) { |
| parse.zpad = parse.width; |
| } |
| |
| if (parse.precis > parse.zpad) { |
| parse.zpad = parse.precis; |
| } |
| |
| /* check for presence of l flag (e.g., %ld) */ |
| if (c == 'l' || c == 'L') { |
| parse.lFlag = true; |
| c = fmt[findex++]; |
| } |
| else { |
| parse.lFlag = false; |
| } |
| |
| parse.str = ""; |
| parse.len = 0; |
| |
| int val = aindex >= args.length ? 0 : args[aindex++]; |
| |
| if (c == 'd' || c == 'i') { |
| /* signed decimal */ |
| parse.str = formatNum(val, parse.zpad, -10, argSize); |
| parse.len = parse.str.length(); |
| } |
| else if (c == 'u' || /* unsigned decimal */ |
| c == 'x' || /* unsigned hex */ |
| c == 'X' || /* uppercase hex */ |
| c == 'o') { /* unsigned octal */ |
| int base = 10; |
| if (c == 'x' || c == 'X') { |
| base = 16; |
| } |
| else if (c == 'o') { |
| base = 8; |
| } |
| |
| if (c == 'X') { |
| /* Parse using upper case digits */ |
| parse.str = formatNum(val, parse.zpad, base, argSize, true); |
| } |
| else { |
| /* Parse using lower case digits */ |
| parse.str = formatNum(val, parse.zpad, base, argSize, false); |
| } |
| |
| parse.len = parse.str.length(); |
| } |
| else if (c == 'p') { |
| parse.zpad = 2 * argSize; |
| parse.str = formatNum( |
| val, |
| parse.zpad, 16, |
| argSize); |
| parse.str = "@" + parse.str; |
| parse.len = parse.str.length(); |
| } |
| else if (c == 'c') { |
| /* character */ |
| parse.str = Character.toString((char)val); |
| parse.len = 1; |
| } |
| else if (c == 'r') { |
| /* lookup symbol table symbol */ |
| parse.str = lookupSymbol(val); |
| parse.len = parse.str.length(); |
| } |
| else if (c == 's') { |
| /* string */ |
| /* substitute (null) for NULL pointer */ |
| if (val == 0) { |
| parse.str = "(null)"; |
| } |
| else { |
| parse.str = lookupString(val); |
| } |
| parse.len = parse.str.length(); |
| |
| if (parse.precis != -1 && parse.precis < parse.len) { |
| parse.len = parse.precis; |
| } |
| } |
| /* Check for extended format types */ |
| else if (c == '$') { |
| c = fmt[findex++]; |
| |
| /* Call site, displayed as a file and line number. */ |
| if (c == 'F') { |
| |
| /* The next argument should contain the line number. */ |
| int lineNum = args[aindex++]; |
| String fileName = (val == 0) |
| ? "(unknown file)" : lookupString(val); |
| |
| /* |
| * In respecting the maximum length, we don't want to print |
| * partial file names or line numbers. |
| */ |
| if (parse.precis != -1) { |
| /* Retrieve the file name string */ |
| parse.str = "\"" + fileName + "\""; |
| |
| parse.len = parse.str.length(); |
| |
| /* If the file name is too long, drop it. */ |
| if (parse.len > parse.precis) { |
| parse.len = 0; |
| } |
| else { |
| String line = ", line " + lineNum + ": "; |
| /* If the line number fits, add it. */ |
| if ((parse.len + line.length()) <= parse.precis) { |
| parse.str += line; |
| parse.len = parse.str.length(); |
| } |
| } |
| } |
| /* |
| * If no precision (maximum length) is specified, just |
| * create and return the string |
| */ |
| else { |
| parse.str = "\"" + fileName + "\"" + |
| ", line " + lineNum + ": "; |
| parse.len = parse.str.length(); |
| } |
| } |
| /* Recursive format string. */ |
| else if (c == 'S') { |
| /* Retrieve the format string. */ |
| String recurFmt = lookupString(val); |
| |
| /* Create a new array to store the rest of the args. */ |
| int[] recurArgs = new int[args.length - aindex]; |
| |
| /* Copy the remaining args into the new array. */ |
| System.arraycopy(args, aindex, recurArgs, 0, recurArgs.length); |
| |
| /* Perform a recursive format */ |
| Result temp = doPrintRecur(recurFmt, recurArgs, argSize); |
| |
| /* Store the formatted string in the parse object. */ |
| parse.str = temp.result; |
| parse.len = parse.str.length(); |
| |
| /* |
| * Update the arg index based on the number of arguments |
| * consumed by the recursive call. |
| */ |
| aindex += temp.numArgsConsumed; |
| } |
| } |
| else { |
| /* unknown format, just output the character */ |
| parse.str = Character.toString(c); |
| parse.len = 1; |
| aindex--; /* unget the value */ |
| } |
| |
| /* compute number of characters left in field */ |
| parse.width -= parse.len; |
| |
| if (!parse.lJust) { |
| /* pad with blanks on left */ |
| while (--parse.width >= 0) { |
| res.result += " "; |
| } |
| } |
| |
| /* |
| * Output number, character or string. Only output as many |
| * characters as parse.len. |
| */ |
| res.result += parse.str.substring(0, parse.len); |
| |
| /* pad with blanks on right */ |
| if (parse.lJust) { |
| while (--parse.width >= 0) { |
| res.result += " "; |
| } |
| } |
| |
| } /* while */ |
| |
| /* |
| * Return the number of arguments consumed through the |
| * 'numArgsConsumed' parameter. |
| */ |
| res.numArgsConsumed = aindex; |
| |
| /* Return the formatted string. */ |
| return (res); |
| } |
| |
| /* |
| * ======== formatNum ======== |
| */ |
| static String formatNum(int val, int zpad, int base, int argSize) |
| { |
| return (formatNum(val, zpad, base, argSize, false)); |
| } |
| |
| /* |
| * ======== formatNum ======== |
| * Converts 'val' into a string. |
| * |
| * The 'argSize' is used here to correctly upcast the 'val' to a 64-bit |
| * long, while handling sign extension. |
| * |
| * 'base' is, e.g, -10, 10, 16 |
| */ |
| static String formatNum(int val, int zpad, int base, int argSize, boolean caps) |
| { |
| String res = ""; |
| int i = 0; |
| char sign = '\0'; |
| |
| char[] upper = { |
| '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', |
| 'A', 'B', 'C', 'D', 'E', 'F' |
| }; |
| |
| char[] lower = { |
| '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', |
| 'a', 'b', 'c', 'd', 'e', 'f' |
| }; |
| |
| char [] digtohex; |
| |
| if (caps) { |
| digtohex = upper; |
| } |
| else { |
| digtohex = lower; |
| } |
| |
| long n; |
| |
| /* If 'val' is positive, we can simply cast it. */ |
| if (val >= 0) { |
| n = val; |
| } |
| /* |
| * Otherwise, if val is negative and the type is signed, |
| * we can just cast it. We remove the sign for conversion purposes |
| * (the value will be used to index into an array), and account for |
| * it separately. |
| */ |
| else if (base < 0) { |
| n = -val; |
| /* account for sign '-': ok since zpad is signed */ |
| --zpad; |
| sign = '-'; |
| } |
| /* |
| * Otherwise, if val is negative and the type is unsigned, we |
| * need to strip the sign bits from the value after casting it. |
| * Casting it will sign-extend the value, which is incorrect, so |
| * we must remove the sign extension bits. |
| */ |
| else { |
| n = val; |
| n += (0x1L << (8 * argSize)); |
| } |
| |
| /* |
| * Remove the negative sign from 'base' (the base value will |
| * be used in calculating an index to an array). |
| */ |
| if (base < 0) { |
| base = -base; |
| } |
| |
| /* Compute digits in the number from right to left. */ |
| do { |
| res = digtohex[(int)(n % base)] + res; |
| n = n / base; |
| ++i; |
| } while (n != 0); |
| |
| /* pad with leading 0s on left */ |
| while (i < zpad) { |
| res = '0' + res; |
| ++i; |
| } |
| |
| /* add sign indicator */ |
| if (sign != '\0') { |
| res = sign + res; |
| } |
| |
| return (res); |
| } |
| |
| /* |
| * ======== lookupString ======== |
| */ |
| private static String lookupString(long addr) |
| { |
| /* |
| * Convert address to unsigned. |
| * The arguments are signed IArgs, so the address may be negative and |
| * need to be converted. |
| */ |
| if (addr < 0) { |
| addr += Math.pow(2, argSize); |
| } |
| |
| if (ofReader != null) { |
| String str = ofReader.findString(addr); |
| if (str != null) { |
| return (str); |
| } |
| } |
| |
| /* can't find a static string */ |
| return ("<String @" + Long.toHexString(addr) + ">"); |
| } |
| |
| /* |
| * ======== lookupSymbol ======== |
| * Lookup symbol name in symbol table |
| */ |
| private static String lookupSymbol(long addr) |
| { |
| /* |
| * Convert address to unsigned. |
| * The arguments are signed IArgs, so the address may be negative and |
| * need to be converted. |
| */ |
| if (addr < 0) { |
| addr += Math.pow(2, 32); |
| } |
| |
| if (symTab != null) { |
| String symbols[] = null; |
| /* Try code symbol first. */ |
| symbols = symTab.lookupFuncName(addr); |
| if ((symbols != null) && (symbols.length > 0)) { |
| return (symbols[0]); |
| } |
| |
| /* If no code symbol, try data symbol. */ |
| symbols = symTab.lookupDataSymbol(addr); |
| if ((symbols != null) && (symbols.length > 0)) { |
| return (symbols[0]); |
| } |
| } |
| |
| /* can't find symbol name */ |
| return ("<Symbol @" + Long.toHexString(addr) + ">"); |
| } |
| |
| |
| /* This dummy class is used to help test the format code. */ |
| public class TestOFReader implements xdc.rta.IOFReader |
| { |
| public static final int FILE_NAME = 1; |
| public static final int FORMAT_STRING = 2; |
| |
| |
| /* Parse and close are unused. */ |
| public String parse(String fileName) throws IOException { |
| return null; |
| } |
| public void close() throws IOException { |
| } |
| |
| /* |
| * ======== findString ======== |
| * This implementation returns hardcoded strings |
| */ |
| public String findString(long addr) { |
| switch ((int) addr) { |
| case FILE_NAME: |
| return ("MyCode.c"); |
| case FORMAT_STRING: |
| return ("Test format str %d %d %d"); |
| default: |
| return ("Bad string value!"); |
| } |
| } |
| } |
| |
| /* |
| * ======== main ======== |
| * Simple unit test of doPrint |
| */ |
| static public void main(String [] args) |
| { |
| String [] fmts = { |
| "%%d test: %0.3d, %.3d, %3d, '%-3d', '%-0.3d'\n", |
| "%%x test: %0.3x, %.3x, %3x, '%-3x', '%-0.3x'\n", |
| "%%X test: %0.3X, %.3X, %3X, '%-3X', '%-0.3X'\n", |
| "%%o test: %0.3o, %.3o, %3o, '%-3o', '%-0.3o'\n", |
| "%%p test: %0.3p, %.3p, %3p, '%-3p', '%-0.3p'\n", |
| "%%u test: %0.3u, %.3u, %3u, '%-3u', '%-0.3u'\n", |
| "too many %%'s test: %x, %x, %x, %x, %x, %x, %x\n", |
| "%%* test: '%0.*d', '%*.*d'\n", |
| }; |
| |
| int [] argv = {1, 2, 3, 4, 5}; |
| for (int i = 0; i < fmts.length; i++) { |
| System.out.print(doPrint(fmts[i], argv)); |
| System.out.print(doPrint(fmts[i], argv, 2)); |
| } |
| |
| argv = new int [] {-1, -2, -3, -4, -5}; |
| for (int i = 0; i < fmts.length; i++) { |
| System.out.print(doPrint(fmts[i], argv)); |
| System.out.print(doPrint(fmts[i], argv, 2)); |
| } |
| |
| System.out.print(doPrint( |
| "%%*x test: '%0.*x', '%*.*x', '%*.*x'\n", |
| new int [] {5, 0xcafe, |
| -7, 0, 0xbeef, |
| 7, 0, 0xbabe}, 2)); |
| |
| System.out.print(doPrint( |
| "%%*x test: '%0.*x', '%*.*x', '%*.*x'\n", |
| new int [] {5, 0xcafe, |
| -7, 0, 0xbeef, |
| 7, 0, 0xbabe}, 4)); |
| |
| System.out.print(doPrint( |
| "%%s test: '%*s', '%s'\n", |
| new int [] {10, 0, 17}, 4)); |
| |
| System.out.print(doPrint( |
| "%%r test: '%*r', '%r'\n", |
| new int [] {5, 0, 17}, 4)); |
| |
| /* Test out the %$S and %$F format specifiers. */ |
| TestOFReader testOFReader = (new Formatter()).new TestOFReader(); |
| Formatter.setOFReader(testOFReader); |
| |
| System.out.print(doPrint("Recursive message: %$S, additional arg: %d\n", |
| new int[] {TestOFReader.FORMAT_STRING, 1, 2, 3, 4})); |
| |
| System.out.print(doPrint("%$F%$S\n", |
| new int [] {TestOFReader.FILE_NAME, 124, |
| TestOFReader.FORMAT_STRING, 1, 2, 3})); |
| } |
| } |