There are a number of methods you can implement on your AST nodes to affect how CodeProber interacts with your tool. All APIs should be public Java methods, which CodeProber later accesses via reflection. Some APIs are mandatory, others are optional.
Mandatory API
A small number of methods must be set on the AST nodes, including:
Key | Type | Description |
---|---|---|
getParent | AST Node | Get the parent for the given node, or null if it is the root node |
getChild(int) | AST node | Get a child by index |
In addition, CodeProber requires one of 5 variants of line/column and child counting methods. It checks for the presence of these accessors in numerical order (Variant 1, Variant 2, …). CodeProber has supports for these multiple variants in order to maximize compatibility with different styles of language implementation. If your are able to chose which variant to support, then variant 1 is preferred.
All line/columns values are expected to start at 1. For example, the top-left corner of a text editor is line 1 column 1. The leftmost column of the second line is line 2 column 1, etc. If you do not have location data for a given node, return 0
in both line and column. Certain features in CodeProber, such as the AST node location tracking, treats line 0 column 0 specially.
Variant 1
Key | Type | Description |
---|---|---|
cpr_getStartLine | int | Starting line of the node |
cpr_getStartColumn | int | Starting column of the node |
cpr_getEndLine | int | Ending line of the node |
cpr_getEndColumn | int | Ending column of the node |
getNumChild | int | Get number of children |
Variant 2
Key | Type | Description |
---|---|---|
getStartLine | int | Starting line of the node |
getStartColumn | int | Starting column of the node |
getEndLine | int | Ending line of the node |
getEndColumn | int | Ending column of the node |
getNumChild | int | Get number of children |
Variant 3
Key | Type | Description |
---|---|---|
getBeginLine | int | Starting line of the node |
getBeginColumn | int | Starting column of the node |
getEndLine | int | Ending line of the node |
getEndColumn | int | Ending column of the node |
getNumChildren | int | Get number of children |
Variant 4
Key | Type | Description |
---|---|---|
getStart | int | Starting line and column of the node, packed as 0xLLLLLCCC. For example, line 5 column 7 is 20487 ((5 << 12) + 7 ). |
getEnd | int | Ending line and column of the node, also packed as 0xLLLLLCCC. |
getNumChild | int | Get number of children |
Variant 5
This variant automatically assigns line 0 and column 0 to all nodes. This does not provide a good user experience, only use this if absolutely necessary.
Key | Type | Description |
---|---|---|
getNumChild | int | Get number of children |
Optional API
The following table lists entirely optional APIs. If you are using an API that is not listed here, then it may be deprecated and scheduled for removal. Please open an issue/pull request if you believe the undocumented API you use should be officially maintained.
There are different places where APIs are expected to appear, which are designated by the Where
column in the table. Possible places are:
- “ast” - any AST node
- “root” - the root of the AST
- “any” - any datastructure at all. Used when processing results from property evaluation.
Key | Type | Default Value | Description | Where |
---|---|---|---|---|
cpr_isTALRoot | boolean | false | If true, avoid trying to create TAL locators (see Section 4 in paper) above this node in the AST. This should generally be set to true on a file/compilationUnit level. If implemented at the correct level, you will notice better CodeProber performance and tracking accuracy. |
ast |
cpr_nodeListVisible | boolean | true | If true, show this node in the list of nodes when you right click to create a new probe. | ast |
cpr_propSearchVisible | boolean | true | If true, show this node in the results of search probes. | ast |
cpr_cutoffNodeListTree | boolean | false | If true, avoid showing the subtree starting at this node in the list of nodes when you right click to create a new probe. You likely want to set this to true for file/compilationUnit nodes that are not the CodeProber input file. |
ast |
cpr_isInsideExternalFile | boolean | false | If true the AST node appears in a part of the AST that was not parsed from the CodeProber file. This adds a small indicator in the UI to show that it is external, and improves node tracking for these nodes, since they are not expected to move around when the user makes changes in the CodeProber editor. |
ast |
cpr_nodeLabel | String | null | A label that is shown in the UI rather than the actual Java class of the node. Highly related to cpr.type_identification_style in system_properties. |
ast |
cpr_astLabel | String | null | A label to show in the AST view in relation to the node. Can be used to for example show the value of an variable reference or literal. Do not place very long strings here, it will make the AST view quite cramped. | ast |
cpr_propertyListShow | String[] or List<String> |
null | An list of property names to add in the list of properties when creating a new probe. By default, CodeProber will show anything annotated with an ASTNodeAnnotation.Attribute annotation, as is the case for JastAdd. With cpr_propertyListShow you can name properties that aren’t annotated with @Attribute but that you still want to show in the list. The names can also be “labelled properties”, which are arbitrary strings prefixed by l: . The values after l: get passed to cpr_lInvoke when they should get invoked. Labelled properties can be used to introduce properties in the UI that aren’t “real” Java methods. This can be useful in “wrapper” implementations. |
ast |
cpr_lInvoke(String) | any | null | Invoked when a labelled property from cpr_propertyListShow is being evaluated. The argument is the string after l: . |
ast |
cpr_extraAstReferences | String[] or List<String> |
null | A list of property names (like cpr_propertyListShow ) that resolve to AST node references that should be included as extra connections in the AST view. For example this can be used to add higher-order attributes, or point out remote references betwen nodes in the AST. |
ast |
cpr_lGetChildName(String) | String | null | In JastAdd, child accessor methods have an annotation that describes the name of the child in the AST specification. This name is extracted and rendered in the AST view. In addition, all child accessors are placed on top of the list of properties when creating a new probe. cpr_lGetChildName can be used to make a labelled property be treated as a child accessor. It is invoked with the value after l: , like cpr_lInvoke . |
ast |
cpr_lGetAspectName(String) | String | null | In JastAdd, attributes have an annotation that describe which aspect declaration they belong to. This is used in the CodeProber UI to group the list of properties when creating a new probe. cpr_lGetAspectName can be used to make a labelled property get an aspect grouping too. It is invoked with the value after l: , like cpr_lInvoke . |
ast |
cpr_setTraceReceiver(Consumer) | void | Install a trace receiver, which should be invoked whenever an attribute begins of finishes evaluation. This is part of the “Capture traces” checkbox in the CodeProber UI. See Capture Traces section below for more detail. |
root | |
cpr_getOutput | any | Overrides how something is presented in the result of a probe evaluation. CodeProber supports many standard datastructures by default, including arrays, and anythign that implements java.util.Collection or java.lang.Iterable . However, custom datastructures can use cpr_getOutput to extract which parts |
any | |
cpr_getDiagnostic | String | Similar to cpr_getOutput , this lets you associate a diagnostic string with the result of a probe evaluation. These format for diagnostic strings are documented in the CodeProber ui: click the triple-dot menu in a probe, followed by Magic output messages help . For example, to render a warning (yellow) squiggly between line 5 column 7 is and line 5 column 10, cpr_getDiagnostic should return WARN@20487;20490;Hover message here . The numbers are packed line/column bits, like “Variant 3” in the Mandatory API above. See “Diagnostic class” below for example of how cpr_getOutput and cpr_getDiagnostic can be used. |
any | |
cpr_ppPrefix | String | null | Add a prefix to the “Pretty Print” view. This appears before the first child value. | ast |
cpr_ppInfix(int) | String | null | Add an infix between children in the “Pretty Print view”. The argument is in the range [0, num_children - 1] . Between child 0 and 1 the argument is 0 , between child 1 and 2 the argument is 1, etc. |
ast |
cpr_ppPrefix | String | null | Add a suffix to the “Pretty Print” view. This appears after the last child value. | ast |
cpr_describeParentConnection | Object[] |
null | Describe how a given node is connected to its parent node. This is used during node locator construction. It is intended to be used to connect results of higher-order attributes to their origin. In JastAdd-based tools, CodeProber can infer this automatically At the time of writing the returned array must contain exactly one string, which is the name of a higher-order attribute. If required, this can be expanded to support for example parameterized attributes too. | ast |
Diagnostic class
A common use case for CodeProber is to inspect the result of semantic analysis. The results of such an analysis can both be presented as squiggly lines in the editor, and as text in a probe result area. This is achieved by having a class that implements both the cpr_getOutput
and cpr_getDiagnostic
APIs, like this:
public class Diagnostic {
Object humanReadable;
String diagnostic;
public Diagnostic(Object humanReadable, String diagnostic) {
this.humanReadable = humanReadable;
this.diagnostic = diagnostic;
}
public Object cpr_getOutput() { return humanReadable; }
public String cpr_getDiagnostic() { return diagnostic; }
}
The two fields in Diagnostic
represent the probe result area and text editor, respectively. For example (using JastAdd syntax), the following attribute will create a error squiggly line between line 3, column 5 and line 3, column 7:
syn Object Node.demo() = new Diagnostic(
"This is shown in the probe window"
String.format("ERR@%d;%d;%s", (3 << 12) + 5, (3 << 12) + 7, "This is shown when the squiggly is hovered")
);
Capture Traces
The cpr_setTraceReceiver
API requires a little more explanation.
In the CodeProber settings panel there is a checkbox with the label Capture traces
. This allows you to capture information about indirect dependencies of properties. For this to work, the AST root must have a function cpr_setTraceReceiver
that looks like this:
public void cpr_setTraceReceiver(java.util.function.Consumer<Object[]> recv) {
// 'recv' records trace information.
// You should assign it to a field for later use.
this.theTraceReceiver = recv;
}
// Later, when something should be added to a trace, call it
// It will be non-null if `Capture traces` is checked.
if (this.theTraceReceiver != null) {
this.theTraceReceiver.accept(new Object[]{ ... })
}
If recv
is called while CodeProber is evaluating a property, then the information there will be visible in the CodeProber UI, if the user has checked Capture traces
.
Kinds of trace events
CodeProber will toString
the first element in the object array to identify the kinds of trace event. Currently, two types of events are supported, and they match tracing events produced by JastAdd:
COMPUTE_BEGIN
Expected structure:
["COMPUTE_BEGIN", ASTNode node, String attribute, Object params, Object value]
Example invocation to recv
in cpr_setTraceReceiver
:
.accept(new Object[]{ "COMPUTE_BEGIN", someAstNode, "foo()", null, null }) recv
COMPUTE_END
Expected structure:
["COMPUTE_END", ASTNode node, String attribute, Object params, Object value]
Example invocation to recv
in cpr_setTraceReceiver
:
.accept(new Object[]{ "COMPUTE_END", someAstNode, "foo()", null, "ResultValue" }) recv
Tracing locator issues
Tracing can be tricky to get right. You may get errors in the terminal where you started codeprober.jar
stating something like:
Failed creating locator for AstNode< [..]
This happens if one of the AST nodes passed to a trace events aren’t attached to the AST anymore. This can happen for example if you mutate the tree through rewrites. You can try toggling the flush tree first
checkbox under Capture traces
on and off. You can also try changing the cache strategy
values back and forth. Some combination of the two might work.
If changing the settings doesn’t work, then you must change which events are reported to CodeProber. Try to avoid setting the ASTNode
arguments to nodes that get removed from the tree.
Supporting Tracing In JastAdd
In JastAdd, compile with the flag --tracing=all
, and then implement cpr_setTraceReceiver
as follows:
public void Program.cpr_setTraceReceiver(final java.util.function.Consumer<Object[]> recv) {
trace().setReceiver(new ASTState.Trace.Receiver() {
@Override
public void accept(ASTState.Trace.Event event, ASTNode node, String attribute, Object params, Object value) {
recv.accept(new Object[] { event, node, attribute, params, value });
}
});
}