All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.escapevelocity.Macro Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2018 Google, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.escapevelocity;

import com.google.common.base.Verify;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * A macro definition. Macros appear in templates using the syntax {@code #macro (m $x $y) ... #end}
 * and each one produces an instance of this class. Evaluating a macro involves setting the
 * parameters (here {$x $y)} and evaluating the macro body. Macro arguments are call-by-name, which
 * means that we need to set each parameter variable to the node in the parse tree that corresponds
 * to it, and arrange for that node to be evaluated when the variable is actually referenced.
 *
 * 

There are two ways to invoke a macro. {@code #m('foo', 'bar')} sets $x and $y. {@code * #@m('foo', 'bar') ... #end} sets $x and $y, and also sets $bodyContent to the template text * {@code ...}. * * @author [email protected] (Éamonn McManus) */ class Macro { private final int definitionLineNumber; private final String name; private final ImmutableList parameterNames; private final Node macroBody; Macro(int definitionLineNumber, String name, List parameterNames, Node macroBody) { this.definitionLineNumber = definitionLineNumber; this.name = name; this.parameterNames = ImmutableList.copyOf(parameterNames); this.macroBody = macroBody; } int parameterCount() { return parameterNames.size(); } /** * Renders a call to this macro with the arguments in {@code thunks} and with a possibly-null * {@code bodyContent}. The {@code bodyContent} is non-null if the macro call looks like * {@code #@foo(...) ... #end}; the {@code #@} indicates that the text up to the matching * {@code #end} should be made available as the variable {@code $bodyContent} inside the macro. */ void render( EvaluationContext context, List thunks, Node bodyContent, StringBuilder output) { try { Verify.verify(thunks.size() == parameterNames.size(), "Argument mismatch for %s", name); Map parameterThunks = new LinkedHashMap<>(); for (int i = 0; i < parameterNames.size(); i++) { parameterThunks.put(parameterNames.get(i), thunks.get(i)); } EvaluationContext newContext = new MacroEvaluationContext(parameterThunks, context, bodyContent); macroBody.render(newContext, output); } catch (EvaluationException e) { EvaluationException newException = new EvaluationException( "In macro #" + name + " defined on line " + definitionLineNumber + ": " + e.getMessage()); newException.setStackTrace(e.getStackTrace()); throw newException; } } /** * The context for evaluation within macros. This wraps an existing {@code EvaluationContext} * but intercepts reads of the macro's parameters so that they result in a call-by-name evaluation * of whatever was passed as the parameter. For example, if you write... *

{@code
   * #macro (mymacro $x)
   * $x $x
   * #end
   * #mymacro($foo.bar(23))
   * }
* ...then the {@code #mymacro} call will result in {@code $foo.bar(23)} being evaluated twice, * once for each time {@code $x} appears. The way this works is that {@code $x} is a thunk. * Historically a thunk is a piece of code to evaluate an expression in the context where it * occurs, for call-by-name procedures as in Algol 60. Here, it is not exactly a piece of code, * but it has the same responsibility. */ static class MacroEvaluationContext implements EvaluationContext { private final Map parameterThunks; private final EvaluationContext originalEvaluationContext; private final Node bodyContent; MacroEvaluationContext( Map parameterThunks, EvaluationContext originalEvaluationContext, Node bodyContent) { this.parameterThunks = parameterThunks; this.originalEvaluationContext = originalEvaluationContext; this.bodyContent = bodyContent; } @Override public Object getVar(String var) { if (bodyContent != null && var.equals("bodyContent")) { return bodyContent; } ExpressionNode thunk = parameterThunks.get(var); if (thunk == null) { return originalEvaluationContext.getVar(var); } else { // Evaluate the thunk in the context where it appeared, not in this context. Otherwise // if you pass $x to a parameter called $x you would get an infinite recursion. Likewise // if you had #macro(mymacro $x $y) and a call #mymacro($y 23), you would expect that $x // would expand to whatever $y meant at the call site, rather than to the value of the $y // parameter. return thunk.evaluate(originalEvaluationContext); } } @Override public boolean varIsDefined(String var) { return parameterThunks.containsKey(var) || (bodyContent != null && var.equals("bodyContent")) || originalEvaluationContext.varIsDefined(var); } @Override public Runnable setVar(final String var, Object value) { // Copy the behaviour that #set will shadow a macro parameter, even though the Velocity peeps // seem to agree that that is not good. final ExpressionNode thunk = parameterThunks.get(var); if (thunk == null) { return originalEvaluationContext.setVar(var, value); } else { parameterThunks.remove(var); final Runnable originalUndo = originalEvaluationContext.setVar(var, value); return () -> { originalUndo.run(); parameterThunks.put(var, thunk); }; } } @Override public ImmutableSet publicMethodsWithName(Class startClass, String name) { return originalEvaluationContext.publicMethodsWithName(startClass, name); } @Override public Map getMacros() { return originalEvaluationContext.getMacros(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy