in freemarker-core/src/main/java/freemarker/core/Environment.java [906:1095]
private void setMacroContextLocalsFromArguments(
final Macro.Context macroCtx,
final Macro macro,
final Map<String, ? extends Expression> namedArgs, final List<? extends Expression> positionalArgs)
throws TemplateException {
String catchAllParamName = macro.getCatchAll();
SimpleHash namedCatchAllParamValue = null;
SimpleSequence positionalCatchAllParamValue = null;
int nextPositionalArgToAssignIdx = 0;
// Used for ?with_args(...):
WithArgsState withArgsState = getWithArgState(macro);
if (withArgsState != null) {
TemplateHashModelEx byNameWithArgs = withArgsState.byName;
TemplateSequenceModel byPositionWithArgs = withArgsState.byPosition;
if (byNameWithArgs != null) {
TemplateHashModelEx2.KeyValuePairIterator withArgsKVPIter
= TemplateModelUtils.getKeyValuePairIterator(byNameWithArgs);
while (withArgsKVPIter.hasNext()) {
TemplateHashModelEx2.KeyValuePair withArgKVP = withArgsKVPIter.next();
String argName;
{
TemplateModel argNameTM = withArgKVP.getKey();
if (!(argNameTM instanceof TemplateScalarModel)) {
throw new _TemplateModelException(
"Expected string keys in the \"with args\" hash, but one of the keys was ",
new _DelayedAOrAn(new _DelayedFTLTypeDescription(argNameTM)), ".");
}
argName = EvalUtil.modelToString((TemplateScalarModel) argNameTM, null, null);
}
TemplateModel argValue = withArgKVP.getValue();
// What if argValue is null? It still has to occur in the named catch-all parameter, to be similar
// to <@macroWithCatchAll a=null b=null />, which will also add the keys to the catch-all hash.
// Similarly, we also still fail if the name is not declared.
final boolean isArgNameDeclared = macro.hasArgNamed(argName);
if (isArgNameDeclared) {
macroCtx.setLocalVar(argName, argValue);
} else if (catchAllParamName != null) {
if (namedCatchAllParamValue == null) {
namedCatchAllParamValue = initNamedCatchAllParameter(macroCtx, catchAllParamName);
}
if (!withArgsState.orderLast) {
namedCatchAllParamValue.put(argName, argValue);
} else {
List<NameValuePair> orderLastByNameCatchAll = withArgsState.orderLastByNameCatchAll;
if (orderLastByNameCatchAll == null) {
orderLastByNameCatchAll = new ArrayList<>();
withArgsState.orderLastByNameCatchAll = orderLastByNameCatchAll;
}
orderLastByNameCatchAll.add(new NameValuePair(argName, argValue));
}
} else {
throw newUndeclaredParamNameException(macro, argName);
}
} // while (withArgsKVPIter.hasNext())
} else if (byPositionWithArgs != null) {
if (!withArgsState.orderLast) { // ?withArgs
String[] argNames = macro.getArgumentNamesNoCopy();
final int argsCnt = byPositionWithArgs.size();
if (argNames.length < argsCnt && catchAllParamName == null) {
throw newTooManyArgumentsException(macro, argNames, argsCnt);
}
for (int argIdx = 0; argIdx < argsCnt; argIdx++) {
TemplateModel argValue = byPositionWithArgs.get(argIdx);
try {
if (nextPositionalArgToAssignIdx < argNames.length) {
String argName = argNames[nextPositionalArgToAssignIdx++];
macroCtx.setLocalVar(argName, argValue);
} else {
if (positionalCatchAllParamValue == null) {
positionalCatchAllParamValue = initPositionalCatchAllParameter(macroCtx, catchAllParamName);
}
positionalCatchAllParamValue.add(argValue);
}
} catch (RuntimeException re) {
throw new _MiscTemplateException(re, this);
}
}
} else { // ?withArgsLast
if (namedArgs != null && !namedArgs.isEmpty() && byPositionWithArgs.size() != 0) {
// Unlike with ?withArgs, here we can't know in general which argument byPositionWithArgs[0]
// meant to refer to, as the named arguments have already taken some indexes.
throw new _MiscTemplateException("Call can't pass parameters by name, as there's " +
"\"with args last\" in effect that specifies parameters by position.");
}
if (catchAllParamName == null) {
// To fail before Expression-s for some normal arguments are evaluated:
int totalPositionalArgCnt =
(positionalArgs != null ? positionalArgs.size() : 0) + byPositionWithArgs.size();
if (totalPositionalArgCnt > macro.getArgumentNamesNoCopy().length) {
throw newTooManyArgumentsException(macro, macro.getArgumentNamesNoCopy(), totalPositionalArgCnt);
}
}
}
}
} // if (withArgsState != null)
if (namedArgs != null) {
if (catchAllParamName != null && namedCatchAllParamValue == null && positionalCatchAllParamValue == null) {
// If a macro call has no argument (like <@m />), before 2.3.30 we assumed it's a by-name call. But now
// if we have ?with_args(args), its argument type decides if the call is by-name or by-position.
if (namedArgs.isEmpty() && withArgsState != null && withArgsState.byPosition != null) {
positionalCatchAllParamValue = initPositionalCatchAllParameter(macroCtx, catchAllParamName);
} else {
namedCatchAllParamValue = initNamedCatchAllParameter(macroCtx, catchAllParamName);
}
}
for (Map.Entry<String, ? extends Expression> argNameAndValExp : namedArgs.entrySet()) {
final String argName = argNameAndValExp.getKey();
final boolean isArgNameDeclared = macro.hasArgNamed(argName);
if (isArgNameDeclared || namedCatchAllParamValue != null) {
final Expression argValueExp = argNameAndValExp.getValue();
TemplateModel argValue = argValueExp.eval(this);
if (isArgNameDeclared) {
macroCtx.setLocalVar(argName, argValue);
} else {
namedCatchAllParamValue.put(argName, argValue);
}
} else {
if (positionalCatchAllParamValue != null) {
throw newBothNamedAndPositionalCatchAllParamsException(macro);
} else {
throw newUndeclaredParamNameException(macro, argName);
}
}
}
} else if (positionalArgs != null) {
if (catchAllParamName != null && positionalCatchAllParamValue == null && namedCatchAllParamValue == null) {
if (positionalArgs.isEmpty() && withArgsState != null && withArgsState.byName != null) {
namedCatchAllParamValue = initNamedCatchAllParameter(macroCtx, catchAllParamName);
} else {
positionalCatchAllParamValue = initPositionalCatchAllParameter(macroCtx, catchAllParamName);
}
}
String[] argNames = macro.getArgumentNamesNoCopy();
final int argsCnt = positionalArgs.size();
final int argsWithWithArgsCnt = argsCnt + nextPositionalArgToAssignIdx;
if (argNames.length < argsWithWithArgsCnt && positionalCatchAllParamValue == null) {
if (namedCatchAllParamValue != null) {
throw newBothNamedAndPositionalCatchAllParamsException(macro);
} else {
throw newTooManyArgumentsException(macro, argNames, argsWithWithArgsCnt);
}
}
for (int srcPosArgIdx = 0; srcPosArgIdx < argsCnt; srcPosArgIdx++) {
Expression argValueExp = positionalArgs.get(srcPosArgIdx);
TemplateModel argValue;
try {
argValue = argValueExp.eval(this);
} catch (RuntimeException e) {
throw new _MiscTemplateException(e, this);
}
if (nextPositionalArgToAssignIdx < argNames.length) {
String argName = argNames[nextPositionalArgToAssignIdx++];
macroCtx.setLocalVar(argName, argValue);
} else {
positionalCatchAllParamValue.add(argValue);
}
}
} // else if (positionalArgs != null)
if (withArgsState != null && withArgsState.orderLast) {
if (withArgsState.orderLastByNameCatchAll != null) {
for (NameValuePair nameValuePair : withArgsState.orderLastByNameCatchAll) {
if (!namedCatchAllParamValue.containsKey(nameValuePair.name)) {
namedCatchAllParamValue.put(nameValuePair.name, nameValuePair.value);
}
}
} else if (withArgsState.byPosition != null) {
TemplateSequenceModel byPosition = withArgsState.byPosition;
int withArgCnt = byPosition.size();
String[] argNames = macro.getArgumentNamesNoCopy();
for (int withArgIdx = 0; withArgIdx < withArgCnt; withArgIdx++) {
TemplateModel withArgValue = byPosition.get(withArgIdx);
if (nextPositionalArgToAssignIdx < argNames.length) {
String argName = argNames[nextPositionalArgToAssignIdx++];
macroCtx.setLocalVar(argName, withArgValue);
} else {
// It was checked much earlier that we don't have too many arguments, so this must work:
positionalCatchAllParamValue.add(withArgValue);
}
}
}
}
}