/* * Copyright 2011 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.template.soy.soytree; import com.google.template.soy.base.SourceLocation; import com.google.template.soy.basetree.CopyState; import com.google.template.soy.basetree.MixinParentNode; import com.google.template.soy.data.SanitizedContent.ContentKind; import com.google.template.soy.error.ErrorReporter; import com.google.template.soy.error.ErrorReporter.Checkpoint; import com.google.template.soy.error.SoyErrorKind; import com.google.template.soy.soytree.SoyNode.RenderUnitNode; import com.google.template.soy.types.primitive.SanitizedType; import com.google.template.soy.types.primitive.StringType; import java.util.List; import javax.annotation.Nullable; /** * Node representing a 'let' statement with content. * * <p>Important: Do not use outside of Soy code (treat as superpackage-private). * */ public final class LetContentNode extends LetNode implements RenderUnitNode { public static final SoyErrorKind NON_SELF_ENDING_WITH_VALUE = SoyErrorKind.of( "A ''let'' tag should contain a value if and only if it is also self-ending " + "(with a trailing ''/'')."); /** The mixin object that implements the ParentNode functionality. */ private final MixinParentNode<StandaloneNode> parentMixin; /** The let node's content kind, or null if no 'kind' attribute was present. */ @Nullable private final ContentKind contentKind; private LetContentNode( int id, SourceLocation sourceLocation, String localVarName, String commandText, ContentKind contentKind) { super(id, sourceLocation, localVarName, commandText); this.contentKind = contentKind; parentMixin = new MixinParentNode<>(this); } /** * Creates a LetContentNode for a compiler-generated variable. Use this in passes that rewrite the * tree and introduce local temporary variables. */ public static LetContentNode forVariable( int id, SourceLocation sourceLocation, String varName, ContentKind contentKind) { LetContentNode node = new LetContentNode(id, sourceLocation, varName, "$" + varName, contentKind); node.getVar() .setType( contentKind != null ? SanitizedType.getTypeForContentKind(contentKind) : StringType.getInstance()); return node; } /** * Copy constructor. * * @param orig The node to copy. */ private LetContentNode(LetContentNode orig, CopyState copyState) { super(orig, copyState); this.parentMixin = new MixinParentNode<>(orig.parentMixin, this, copyState); this.contentKind = orig.contentKind; } @Override public Kind getKind() { return Kind.LET_CONTENT_NODE; } /** Return The local variable name (without preceding '$'). */ @Override public final String getVarName() { return var.name(); } @Override @Nullable public ContentKind getContentKind() { return contentKind; } // ----------------------------------------------------------------------------------------------- // ParentSoyNode stuff. // Note: Most concrete nodes simply inherit this functionality from AbstractParentCommandNode or // AbstractParentSoyNode. But this class need to include its own MixinParentNode field because // it needs to subclass LetNode (and Java doesn't allow multiple inheritance). @Override public String toSourceString() { StringBuilder sb = new StringBuilder(); sb.append(getTagString()); appendSourceStringForChildren(sb); sb.append("{/").append(getCommandName()).append("}"); return sb.toString(); } @Override public int numChildren() { return parentMixin.numChildren(); } @Override public StandaloneNode getChild(int index) { return parentMixin.getChild(index); } @Override public int getChildIndex(StandaloneNode child) { return parentMixin.getChildIndex(child); } @Override public List<StandaloneNode> getChildren() { return parentMixin.getChildren(); } @Override public void addChild(StandaloneNode child) { parentMixin.addChild(child); } @Override public void addChild(int index, StandaloneNode child) { parentMixin.addChild(index, child); } @Override public void removeChild(int index) { parentMixin.removeChild(index); } @Override public void removeChild(StandaloneNode child) { parentMixin.removeChild(child); } @Override public void replaceChild(int index, StandaloneNode newChild) { parentMixin.replaceChild(index, newChild); } @Override public void replaceChild(StandaloneNode currChild, StandaloneNode newChild) { parentMixin.replaceChild(currChild, newChild); } @Override public void clearChildren() { parentMixin.clearChildren(); } @Override public void addChildren(List<? extends StandaloneNode> children) { parentMixin.addChildren(children); } @Override public void addChildren(int index, List<? extends StandaloneNode> children) { parentMixin.addChildren(index, children); } @Override public void appendSourceStringForChildren(StringBuilder sb) { parentMixin.appendSourceStringForChildren(sb); } @Override public LetContentNode copy(CopyState copyState) { return new LetContentNode(this, copyState); } /** Builder for {@link LetContentNode}. */ public static final class Builder { private static LetContentNode error() { return new LetContentNode(-1, SourceLocation.UNKNOWN, "$error", "$error", null); } private final int id; private final CommandTextParseResult parseResult; private final SourceLocation sourceLocation; /** * @param id The node's id. * @param commandText The node's command text. * @param sourceLocation The node's source location. */ public Builder(int id, CommandTextParseResult parseResult, SourceLocation sourceLocation) { this.id = id; this.parseResult = parseResult; this.sourceLocation = sourceLocation; } /** * Returns a new {@link LetContentNode} built from the builder's state. If the builder's state * is invalid, errors are reported to the {@code errorManager} and {Builder#error} is returned. */ public LetContentNode build(Checkpoint checkpoint, ErrorReporter errorReporter) { if (parseResult.valueExpr != null) { errorReporter.report(sourceLocation, NON_SELF_ENDING_WITH_VALUE); } if (errorReporter.errorsSince(checkpoint)) { return error(); } return new LetContentNode( id, sourceLocation, parseResult.localVarName, parseResult.originalCommandText, parseResult.contentKind); } } }