in src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java [925:1108]
TemplateExpression parseExpression(final JexlInfo info, final String expr, final Scope scope) { // CSOFF: MethodLength
final int size = expr.length();
final ExpressionBuilder builder = new ExpressionBuilder(0);
final StringBuilder strb = new StringBuilder(size);
ParseState state = ParseState.CONST;
int immediate1 = 0;
int deferred1 = 0;
int inner1 = 0;
boolean nested = false;
int inested = -1;
int lineno = info.getLine();
for (int column = 0; column < size; ++column) {
final char c = expr.charAt(column);
switch (state) {
case CONST:
if (c == immediateChar) {
state = ParseState.IMMEDIATE0;
} else if (c == deferredChar) {
inested = column;
state = ParseState.DEFERRED0;
} else if (c == '\\') {
state = ParseState.ESCAPE;
} else {
// do buildup expr
strb.append(c);
}
break;
case IMMEDIATE0: // $
if (c == '{') {
state = ParseState.IMMEDIATE1;
// if chars in buffer, create constant
if (strb.length() > 0) {
final TemplateExpression cexpr = new ConstantExpression(strb.toString(), null);
builder.add(cexpr);
strb.delete(0, Integer.MAX_VALUE);
}
} else {
// revert to CONST
strb.append(immediateChar);
state = ParseState.CONST;
// 'unread' the current character
column -= 1;
continue;
}
break;
case DEFERRED0: // #
if (c == '{') {
state = ParseState.DEFERRED1;
// if chars in buffer, create constant
if (strb.length() > 0) {
final TemplateExpression cexpr = new ConstantExpression(strb.toString(), null);
builder.add(cexpr);
strb.delete(0, Integer.MAX_VALUE);
}
} else {
// revert to CONST
strb.append(deferredChar);
state = ParseState.CONST;
// 'unread' the current character
column -= 1;
continue;
}
break;
case IMMEDIATE1: // ${...
if (c == '}') {
if (immediate1 > 0) {
immediate1 -= 1;
strb.append(c);
} else {
// materialize the immediate expr
final String src = strb.toString();
final TemplateExpression iexpr = new ImmediateExpression(
src,
jexl.parse(info.at(lineno, column), noscript, src, scope),
null);
builder.add(iexpr);
strb.delete(0, Integer.MAX_VALUE);
state = ParseState.CONST;
}
} else {
if (c == '{') {
immediate1 += 1;
}
// do buildup expr
column = append(strb, expr, column, c);
}
break;
case DEFERRED1: // #{...
// skip inner strings (for '}')
// nested immediate in deferred; need to balance count of '{' & '}'
// closing '}'
switch (c) {
case '"':
case '\'':
strb.append(c);
column = StringParser.readString(strb, expr, column + 1, c);
continue;
case '{':
if (expr.charAt(column - 1) == immediateChar) {
inner1 += 1;
strb.deleteCharAt(strb.length() - 1);
nested = true;
} else {
deferred1 += 1;
strb.append(c);
}
continue;
case '}':
// balance nested immediate
if (deferred1 > 0) {
deferred1 -= 1;
strb.append(c);
} else if (inner1 > 0) {
inner1 -= 1;
} else {
// materialize the nested/deferred expr
final String src = strb.toString();
TemplateExpression dexpr;
if (nested) {
dexpr = new NestedExpression(
expr.substring(inested, column + 1),
jexl.parse(info.at(lineno, column), noscript, src, scope),
null);
} else {
dexpr = new DeferredExpression(
strb.toString(),
jexl.parse(info.at(lineno, column), noscript, src, scope),
null);
}
builder.add(dexpr);
strb.delete(0, Integer.MAX_VALUE);
nested = false;
state = ParseState.CONST;
}
break;
default:
// do buildup expr
column = append(strb, expr, column, c);
break;
}
break;
case ESCAPE:
if (c == deferredChar) {
strb.append(deferredChar);
} else if (c == immediateChar) {
strb.append(immediateChar);
} else {
strb.append('\\');
strb.append(c);
}
state = ParseState.CONST;
break;
default: // in case we ever add new unified expression type
throw new UnsupportedOperationException("unexpected unified expression type");
}
if (c == '\n') {
lineno += 1;
}
}
// we should be in that state
if (state != ParseState.CONST) {
// otherwise, we ended a line with a \, $ or #
switch (state) {
case ESCAPE:
strb.append('\\');
strb.append('\\');
break;
case DEFERRED0:
strb.append(deferredChar);
break;
case IMMEDIATE0:
strb.append(immediateChar);
break;
default:
throw new Exception(info.at(lineno, 0), "malformed expression: " + expr, null);
}
}
// if any chars were buffered, add them as a constant
if (strb.length() > 0) {
final TemplateExpression cexpr = new ConstantExpression(strb.toString(), null);
builder.add(cexpr);
}
return builder.build(this, null);
}