/* * 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.common.base.Preconditions; import com.google.template.soy.base.SourceLocation; import com.google.template.soy.basetree.CopyState; import com.google.template.soy.data.SanitizedContent.ContentKind; import com.google.template.soy.data.internalutils.NodeContentKinds; import com.google.template.soy.error.SoyErrorKind; import com.google.template.soy.exprparse.ExpressionParser; import com.google.template.soy.exprparse.SoyParsingContext; import com.google.template.soy.exprtree.ExprRootNode; import com.google.template.soy.soytree.CommandTextAttributesParser.Attribute; import com.google.template.soy.soytree.SoyNode.LocalVarInlineNode; import com.google.template.soy.soytree.SoyNode.StandaloneNode; import com.google.template.soy.soytree.SoyNode.StatementNode; import com.google.template.soy.soytree.defn.LocalVar; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nullable; /** * Abstract node representing a 'let' statement. * * <p>Important: Do not use outside of Soy code (treat as superpackage-private). * */ public abstract class LetNode extends AbstractCommandNode implements StandaloneNode, StatementNode, LocalVarInlineNode { public static final SoyErrorKind INVALID_COMMAND_TEXT = SoyErrorKind.of("Invalid ''let'' command text."); /** Return value for {@code parseCommandTextHelper()}. */ public static final class CommandTextParseResult { final String originalCommandText; /** The parsed local var name (without '$'). */ final String localVarName; /** The parsed value expr, or null if none. */ @Nullable final ExprRootNode valueExpr; /** The parsed param's content kind, or null if none. */ @Nullable public final ContentKind contentKind; private CommandTextParseResult( String originalCommandText, String localVarName, @Nullable ExprRootNode valueExpr, @Nullable ContentKind contentKind) { this.originalCommandText = originalCommandText; this.localVarName = localVarName; this.valueExpr = valueExpr; this.contentKind = contentKind; } } /** Pattern for a variable name plus optional value or attributes (but not both). */ // Note: group 1 = local var name, group 2 = value expr (or null), group 3 = trailing attributes // (or null). private static final Pattern COMMAND_TEXT_PATTERN = Pattern.compile( "( [$] \\w+ ) (?: \\s* : \\s* (\\S .*) | \\s+ (\\S .*) )?", Pattern.COMMENTS | Pattern.DOTALL); /** Parser for optional attributes in the command text. */ private static final CommandTextAttributesParser ATTRIBUTES_PARSER = new CommandTextAttributesParser( "let", new Attribute("kind", NodeContentKinds.getAttributeValues(), null)); /** The local variable defined by this node. */ protected final LocalVar var; /** * @param id The id for this node. * @param commandText The command text. */ protected LetNode( int id, SourceLocation sourceLocation, String localVarName, String commandText) { super(id, sourceLocation, "let", commandText); this.var = new LocalVar(localVarName, this, null /* type */); } /** * Copy constructor. * * @param orig The node to copy. */ protected LetNode(LetNode orig, CopyState copyState) { super(orig, copyState); this.var = new LocalVar(orig.var, this); } /** * Helper used by subclass constructors to parse the command text. * * @param commandText The command text. * @return An info object containing the parse results. */ public static CommandTextParseResult parseCommandTextHelper( String commandText, SoyParsingContext context, SourceLocation sourceLocation) { Matcher matcher = COMMAND_TEXT_PATTERN.matcher(commandText); if (!matcher.matches()) { context.report(sourceLocation, INVALID_COMMAND_TEXT); return new CommandTextParseResult(commandText, "error", null, null); } String localVarName = new ExpressionParser(matcher.group(1), sourceLocation, context).parseVariable().getName(); String valueExprString = matcher.group(2); ExprRootNode valueExpr = valueExprString != null ? new ExprRootNode( new ExpressionParser(valueExprString, sourceLocation, context).parseExpression()) : null; ContentKind contentKind; if (matcher.group(3 /* optional attributes */) != null) { Preconditions.checkState( matcher.group(2) == null, "Match groups for value expression and optional attributes should be mutually exclusive"); // Parse optional attributes Map<String, String> attributes = ATTRIBUTES_PARSER.parse(matcher.group(3), context, sourceLocation); contentKind = (attributes.get("kind") != null) ? NodeContentKinds.forAttributeValue(attributes.get("kind")) : null; } else { contentKind = null; } return new CommandTextParseResult(commandText, localVarName, valueExpr, contentKind); } /** Gets a unique version of the local var name (e.g. appending "__soy##" if necessary). */ public String getUniqueVarName() { return getVarName() + "__soy" + getId(); } @SuppressWarnings("unchecked") @Override public ParentSoyNode<StandaloneNode> getParent() { return (ParentSoyNode<StandaloneNode>) super.getParent(); } /** Get the local variable defined by this node. */ @Override public final LocalVar getVar() { return var; } }