If you look at the grammar, you can see the following parser rule for ``primaryExpression```
(It's referenced in the tree graph in your question):
primaryExpression
: literal #literalExpression
| identifier STRING #typeConstructor
| CASE valueExpression whenClause+ (ELSE elseExpression=expression)? END #simpleCase
| CASE whenClause+ (ELSE elseExpression=expression)? END #searchedCase
| CAST '(' expression AS type ')' #cast
| ARRAY '[' (expression (',' expression)*)? ']' #arrayConstructor
| MAP '(' (expression ASSIGN expression (',' expression ASSIGN expression)*)? ')' #mapConstructor
| STRUCT '(' (identifier ASSIGN expression (',' identifier ASSIGN expression)*)? ')' #structConstructor
| identifier '(' ASTERISK ')' #functionCall
| identifier '(' (functionArgument (',' functionArgument)* (',' lambdaFunction)*)? ')' #functionCall
| value=primaryExpression '[' index=valueExpression ']' #subscript
| identifier #columnReference
| identifier '.' identifier #qualifiedColumnReference
| base=primaryExpression STRUCT_FIELD_REF fieldName=identifier #dereference
| '(' expression ')' #parenthesizedExpression
;
The alternative that matches your column reference is the #columnReference alternative.
That means, in your listener, you can override the enterColumnReference method, like so (to match ONLY that alternative of the parseExpression rule). It has only a single identifier rule member, so you just reference it:
@Override
public void enterColumnReference(SqlBaseParser.ColumnReferenceContext ctx) {
System.out.println(ctx.identifier().getText());
}
With that override (in addition to yours), I get the following output:
`name`
FNAME
LNAME
This grammar labels all of the alternatives on the parser rules (as least as far as I noticed). This makes it relatively easy to intercept (or listen in) on very specific parser rule alternatives. Each labeled alternative gets it's own enter* and exit* method along with a specific *Context class to make access to the rule members pretty straightforward.
In your case, it appears that you may only be concerned with that alternative IF it's a functionArgument. If that's the case, you can introduce a bit of state management in your listener to track that:
public class MyListener extends SqlBaseBaseListener {
private boolean isFunctionArgument = false;
@Override
public void enterSelectSingle(SqlBaseParser.SelectSingleContext ctx) {
System.out.println(ctx.identifier().getText());
}
@Override
public void enterFunctionArgument(SqlBaseParser.FunctionArgumentContext ctx) {
isFunctionArgument = true;
}
@Override
public void exitListFunctions(SqlBaseParser.ListFunctionsContext ctx) {
isFunctionArgument = true;
}
@Override
public void enterColumnReference(SqlBaseParser.ColumnReferenceContext ctx) {
if (isFunctionArgument) {
System.out.println(ctx.identifier().getText());
}
}
}
In your example, this doesn't change the output, but should give you an idea of how to select more specific usage contexts.
In the sense of "you CAN get there from here", it is possible:
@Override
public void enterSelectSingle(SqlBaseParser.SelectSingleContext ctx) {
System.out.println(ctx.identifier().getText());
SqlBaseParser.BooleanDefaultContext bdc = (SqlBaseParser.BooleanDefaultContext) ctx.expression().booleanExpression();
SqlBaseParser.ValueExpressionDefaultContext cev = (SqlBaseParser.ValueExpressionDefaultContext) bdc.predicated().valueExpression();
SqlBaseParser.FunctionCallContext fc = (SqlBaseParser.FunctionCallContext) cev.primaryExpression();
for (SqlBaseParser.FunctionArgumentContext fa : fc.functionArgument()) {
SqlBaseParser.BooleanDefaultContext bdcfa = (SqlBaseParser.BooleanDefaultContext) fa.expression().booleanExpression();
SqlBaseParser.ValueExpressionDefaultContext cevfa = (SqlBaseParser.ValueExpressionDefaultContext) bdcfa.predicated().valueExpression();
SqlBaseParser.FunctionCallContext fc2 = (SqlBaseParser.FunctionCallContext) cevfa.primaryExpression();
for (SqlBaseParser.FunctionArgumentContext fa2 : fc2.functionArgument()) {
SqlBaseParser.BooleanDefaultContext bdcfa2 = (SqlBaseParser.BooleanDefaultContext) fa2.expression().booleanExpression();
SqlBaseParser.ValueExpressionDefaultContext cevfa2 = (SqlBaseParser.ValueExpressionDefaultContext) bdcfa2.predicated().valueExpression();
SqlBaseParser.ColumnReferenceContext cr = (SqlBaseParser.ColumnReferenceContext) cevfa2.primaryExpression();
System.out.println(cr.identifier().getText());
}
}
}
It's a tough path (and I just directly cast types where you should definitely do an instanceof tests.)
It's also VERY brittle. Any little structure change and this code breaks. Consequently, you'd need a lot of logic to navigate all possible permutations of the parseTree under your singleSelect
A better approach (leveraging what listeners do for you): Notice: I changed enterSelectSingle to exitSelectSingle. You need to wait until you've listened to the child nodes to gather the parameters and print the on the way out.
import java.util.ArrayList;
public class MyListener extends SqlBaseBaseListener {
private boolean isFunctionArgument = false;
private final ArrayList<String> args = new ArrayList<>();
@Override
public void exitSelectSingle(SqlBaseParser.SelectSingleContext ctx) {
System.out.println(ctx.identifier().getText());
for (String arg : args) {
System.out.println(arg);
}
args.clear();
}
@Override
public void enterFunctionArgument(SqlBaseParser.FunctionArgumentContext ctx) {
isFunctionArgument = true;
}
@Override
public void exitListFunctions(SqlBaseParser.ListFunctionsContext ctx) {
isFunctionArgument = true;
}
@Override
public void enterColumnReference(SqlBaseParser.ColumnReferenceContext ctx) {
if (isFunctionArgument) {
args.add(ctx.identifier().getText());
}
}
}
NOTE: even this code (for the sake of simplicity) does not handle nested function calls (for that you'd need to create a stack of ArrayLists, and then push/pop as you enter/exit functions.
I typically write code to visit my parseTree creating the simplified internal tree that I want to deal with in my program, but that's a MUCH longer answer than this, already lengthy, reply.
In short (too late), it can be complex, and you have to deal with that complexity unless you KNOW that you only need to deal with a simpler subset of cases.