At ExaPackets I ran into this problem when writing the compiler for ASN.1 and PML (Protocol Message Language with a syntax partly based on Antlr). This was exacerbated with the fact that you cannot put a breakpoint in String Templates. The problem showed up most when a template called other templates. In those cases, I did not even know what part of the compiler was calling the first template when a bug existed and I had to manually trace the code before I could set a breakpoint and start a debugging session. At times I had to rig a special input file for debugging. My debugging sessions had become very painful and long. I had to find all the calls to a template and figure out when a template was being called because of the recursive nature of computer languages. The solution that I came up with was printing the name of template as a comment in the output. For the cases, that I needed more debugging, I would also print the Java stack trace to print the line number, class and the name of the method of the caller of the templates in the output file. This simple trick made a huge difference and I could simply search the code of compiler for the call to a particular String Template and put a breakpoint in the debugger. The code snippet below uses a class called stream which you can substitute with a writer or stream class of your own.
The solution is really made of two steps:
- Use a single function for rendering templates. I had already done that to make working with String Templates easier and reduce the boilerplate code for rendering templates.
- Print relevant debugging information as comment in the output file produced by the compiler.
For the first step which is rendering the String Template using a single routine, I designed a function that the user passes the Template name and pairs of parameter/value. This way, I could turn debugging on or off in a single line of code. For this, I created writeTemplate function which uses variable arguments with ellipsis. The nice thing about string templates is that it figures out the type and everything can simply be casted to Java Object.
public void writeTemplate(String templateName, Object ... attributes) throws IOException {
if(stream != null) {
if(debug_template) {
stream.write("/* template "+templateName+" */\n");
}
ST template =
Asn1GenCode.createTemplate(
decoderTemplate.templateGroup,
templateName, attributes);
stream.write(renderTemplate(template));
}
}
An examples of a call to writeTemplate is:
In the above example, the compiler is invoking decode_indefinite_length Template and setting lhs to the value of variable lhs, numBits to lengthWidth, etc.
The string template definition of decode_indefinite_length in the above example is:
The definition of renderTemplate called from writeTemplate is shown below. The indentString is a member of stream class.
Now, the output of String Template looks like:
stream.write(renderTemplate(template));
}
}
An examples of a call to writeTemplate is:
decoderStream.writeTemplate("decode_indefinite_length",
"lhs", lhs, "name", name, "numBits",
lengthWidth, "offset", offset, "fieldType", fieldType);
In the above example, the compiler is invoking decode_indefinite_length Template and setting lhs to the value of variable lhs, numBits to lengthWidth, etc.
The string template definition of decode_indefinite_length in the above example is:
decode_indefinite_length(lhs, name, numBits, offset, fieldType) ::= <<
<lhs> = asn1lib_decode_indefinite_length_int(decoder, "<name>", <numBits>, <globals.("asn1Encoding")>, <offset>, <fieldType>);<\n>
>>
The definition of renderTemplate called from writeTemplate is shown below. The indentString is a member of stream class.
public String renderTemplate(ST template) throws IOException {
StringWriter stringWriter = new StringWriter();
STWriter sTWriter = new AutoIndentWriter(stringWriter);
sTWriter.pushIndentation(indentString);
template.write(sTWriter);
String text = stringWriter.toString();
stringWriter.close();
return text;
}
Now, the output of String Template looks like:
/* template decodeHeader */
Asn1libIndexType decode_kv_lte_rrc_990_CounterCheck_v8a0_IEs(Asn1libKeyValueDecoder *decoder) {
/* template seq_component_decls */
Asn1libIndexType _lte_rrc_990_CounterCheck_v8a0_IEs__components[2];
int _lte_rrc_990_CounterCheck_v8a0_IEs__count = 0;
Asn1libIndexType lte_rrc_990_CounterCheck_v8a0_IEs = ASN1_INVALID_INDEX;
/* template seq_read_present_flag */
bool _lte_rrc_990_CounterCheck_v8a0_IEs__lateNonCriticalExtension_present = asn1lib_kv_bit_field_decoder(decoder, 1);
....
Once, I did this, I could look at the generated output and quickly figure out what template was being called which was not easy in the case of templates that were calling other templates. I could search for the template name in the code of the compiler and set my breakpoint. If I need more debugging information, I printed the line number, method and class name of the caller of the template in a function called log. The defintions of the log function and the helper function that it calls are:
/*
* Log the caller of function as a comment
*/
....
Once, I did this, I could look at the generated output and quickly figure out what template was being called which was not easy in the case of templates that were calling other templates. I could search for the template name in the code of the compiler and set my breakpoint. If I need more debugging information, I printed the line number, method and class name of the caller of the template in a function called log. The defintions of the log function and the helper function that it calls are:
/*
* Log the caller of function as a comment
*/
public static void log(Asn1Stream stream, String message) {
if (DEBUG) {
String className = getClassName();
String methodName = getMethodName();
int lineNumber = getLineNumber();
try {
stream.writeln("/* "+className + "." + methodName + "():" + lineNumber+" "+message+"*/\n");
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
* get the line number of the original caller.
* The index is 3 in order to skip two levels of call
* log being one of them.
*/
public static int getLineNumber() {
int lineNumber = Thread.currentThread().getStackTrace() [3].getLineNumber();
return lineNumber;
}
public static String getMethodName() {
String methodName = Thread.currentThread().getStackTrace()[3].getMethodName();
return methodName;
}
public static String getClassName() {
String fullClassName = Thread.currentThread().getStackTrace()[3].getClassName();
String className =
fullClassName.substring(fullClassName.lastIndexOf(".") + 1);
return className;
}