blob: 476cd2413d7cbb622c5674929f770cc73cdc48e5 [file] [log] [blame]
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}));
}
}