in subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/completion/antlr4/ReflectionCompleter.groovy [256:384]
static List<Token> getInvokerTokens(final List<Token> tokens) {
int validIndex = tokens.size()
if (validIndex == 0) {
return []
}
// implementation goes backwards on token list, adding strings
// to be evaluated later
// need to collect using Strings, to support evaluation of string literals
Stack<Integer> expectedOpeners = new Stack<Integer>()
Token lastToken = null
outerloop:
for (Token loopToken in tokens.reverse()) {
switch (loopToken.type) {
// a combination of any of these can be evaluated without side effects
// this just avoids any parentheses,
// could maybe be extended further if harmless parentheses can be detected .
// This allows already a lot of powerful simple completions, like [foo: Baz.bar]['foo'].
case StringLiteral:
// must escape String for evaluation, need the original string e.g. for mapping key
break
case LPAREN:
if (expectedOpeners.empty()) {
break outerloop
}
if (expectedOpeners.pop() != LPAREN) {
return []
}
break
case LBRACK:
if (expectedOpeners.empty()) {
break outerloop
}
if (expectedOpeners.pop() != LBRACK) {
return []
}
break
case RBRACK:
expectedOpeners.push(LBRACK)
break
case RPAREN:
expectedOpeners.push(LPAREN)
break
// tokens which indicate we have reached the beginning of a statement
// operator tokens (must not be evaluated, as they can have side effects via evil overriding
case SPACESHIP:
case EQUAL:
case NOTEQUAL:
case ASSIGN:
case GT:
case LT:
case GE:
case LE:
case ADD:
case ADD_ASSIGN:
case SUB:
case SUB_ASSIGN:
case MUL:
case MUL_ASSIGN:
case DIV:
case DIV_ASSIGN:
case BITOR:
case OR_ASSIGN:
case BITAND:
case AND_ASSIGN:
case XOR:
case XOR_ASSIGN:
case BITNOT:
case OR:
case AND:
case NOT:
case IN:
case INSTANCEOF:
if (expectedOpeners.empty()) {
break outerloop
}
break
// tokens which indicate we have reached the beginning of a statement
case LBRACE:
case SEMI:
// case STRING_CTOR_START:
case GStringBegin:
break outerloop
// tokens we accept
case Identifier:
case CapitalizedIdentifier:
if (lastToken) {
if (lastToken.type == LPAREN) {
//Method invocation,must be avoided
return []
}
if (lastToken.type == Identifier || lastToken.type == CapitalizedIdentifier) {
// could be attempt to invoke closure like 'foo.each bar.baz'
return []
}
}
break
// may begin expression when outside brackets (from back)
case RANGE_INCLUSIVE:
case RANGE_EXCLUSIVE_LEFT:
case RANGE_EXCLUSIVE_RIGHT:
case RANGE_EXCLUSIVE_FULL:
case COLON:
case COMMA:
if (expectedOpeners.empty()) {
break outerloop
}
// harmless literals
case BooleanLiteral:
// case LITERAL_true:
// case LITERAL_false:
case BuiltInPrimitiveType:
// case NUM_INT:
// case NUM_FLOAT:
// case NUM_LONG:
// case NUM_DOUBLE:
// case NUM_BIG_INT:
// case NUM_BIG_DECIMAL:
case METHOD_POINTER:
case DOT:
case SAFE_DOT:
break
default:
return null
} // end switch
validIndex--
lastToken = loopToken
} // end for
return tokens[(validIndex)..-1]
}