/* * Copyright 2008 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.exprtree; import static com.google.common.base.Preconditions.checkArgument; import static com.google.template.soy.exprtree.Operator.Associativity.LEFT; import static com.google.template.soy.exprtree.Operator.Associativity.RIGHT; import static com.google.template.soy.exprtree.Operator.Constants.OPERAND_0; import static com.google.template.soy.exprtree.Operator.Constants.OPERAND_1; import static com.google.template.soy.exprtree.Operator.Constants.OPERAND_2; import static com.google.template.soy.exprtree.Operator.Constants.SP; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableTable; import com.google.errorprone.annotations.Immutable; import com.google.template.soy.base.SourceLocation; import com.google.template.soy.exprtree.ExprNode.OperatorNode; import com.google.template.soy.exprtree.OperatorNodes.AndOpNode; import com.google.template.soy.exprtree.OperatorNodes.ConditionalOpNode; import com.google.template.soy.exprtree.OperatorNodes.DivideByOpNode; import com.google.template.soy.exprtree.OperatorNodes.EqualOpNode; import com.google.template.soy.exprtree.OperatorNodes.GreaterThanOpNode; import com.google.template.soy.exprtree.OperatorNodes.GreaterThanOrEqualOpNode; import com.google.template.soy.exprtree.OperatorNodes.LessThanOpNode; import com.google.template.soy.exprtree.OperatorNodes.LessThanOrEqualOpNode; import com.google.template.soy.exprtree.OperatorNodes.MinusOpNode; import com.google.template.soy.exprtree.OperatorNodes.ModOpNode; import com.google.template.soy.exprtree.OperatorNodes.NegativeOpNode; import com.google.template.soy.exprtree.OperatorNodes.NotEqualOpNode; import com.google.template.soy.exprtree.OperatorNodes.NotOpNode; import com.google.template.soy.exprtree.OperatorNodes.NullCoalescingOpNode; import com.google.template.soy.exprtree.OperatorNodes.OrOpNode; import com.google.template.soy.exprtree.OperatorNodes.PlusOpNode; import com.google.template.soy.exprtree.OperatorNodes.TimesOpNode; import java.util.List; import javax.annotation.Nullable; /** * Enum of Soy expression operators. * * <p>Important: Do not use outside of Soy code (treat as superpackage-private). * */ public enum Operator { NEGATIVE(ImmutableList.of(new Token("-"), OPERAND_0), 8, RIGHT, "- (unary)") { @Override public OperatorNode createNode(SourceLocation location) { return new NegativeOpNode(location); } }, NOT(ImmutableList.of(new Token("not"), SP, OPERAND_0), 8, RIGHT) { @Override public OperatorNode createNode(SourceLocation location) { return new NotOpNode(location); } }, TIMES(ImmutableList.of(OPERAND_0, SP, new Token("*"), SP, OPERAND_1), 7, LEFT) { @Override public OperatorNode createNode(SourceLocation location) { return new TimesOpNode(location); } }, DIVIDE_BY(ImmutableList.of(OPERAND_0, SP, new Token("/"), SP, OPERAND_1), 7, LEFT) { @Override public OperatorNode createNode(SourceLocation location) { return new DivideByOpNode(location); } }, MOD(ImmutableList.of(OPERAND_0, SP, new Token("%"), SP, OPERAND_1), 7, LEFT) { @Override public OperatorNode createNode(SourceLocation location) { return new ModOpNode(location); } }, PLUS(ImmutableList.of(OPERAND_0, SP, new Token("+"), SP, OPERAND_1), 6, LEFT) { @Override public OperatorNode createNode(SourceLocation location) { return new PlusOpNode(location); } }, MINUS(ImmutableList.of(OPERAND_0, SP, new Token("-"), SP, OPERAND_1), 6, LEFT, "- (binary)") { @Override public OperatorNode createNode(SourceLocation location) { return new MinusOpNode(location); } }, LESS_THAN(ImmutableList.of(OPERAND_0, SP, new Token("<"), SP, OPERAND_1), 5, LEFT) { @Override public OperatorNode createNode(SourceLocation location) { return new LessThanOpNode(location); } }, GREATER_THAN(ImmutableList.of(OPERAND_0, SP, new Token(">"), SP, OPERAND_1), 5, LEFT) { @Override public OperatorNode createNode(SourceLocation location) { return new GreaterThanOpNode(location); } }, LESS_THAN_OR_EQUAL(ImmutableList.of(OPERAND_0, SP, new Token("<="), SP, OPERAND_1), 5, LEFT) { @Override public OperatorNode createNode(SourceLocation location) { return new LessThanOrEqualOpNode(location); } }, GREATER_THAN_OR_EQUAL(ImmutableList.of(OPERAND_0, SP, new Token(">="), SP, OPERAND_1), 5, LEFT) { @Override public OperatorNode createNode(SourceLocation location) { return new GreaterThanOrEqualOpNode(location); } }, EQUAL(ImmutableList.of(OPERAND_0, SP, new Token("=="), SP, OPERAND_1), 4, LEFT) { @Override public OperatorNode createNode(SourceLocation location) { return new EqualOpNode(location); } }, NOT_EQUAL(ImmutableList.of(OPERAND_0, SP, new Token("!="), SP, OPERAND_1), 4, LEFT) { @Override public OperatorNode createNode(SourceLocation location) { return new NotEqualOpNode(location); } }, AND(ImmutableList.of(OPERAND_0, SP, new Token("and"), SP, OPERAND_1), 3, LEFT) { @Override public OperatorNode createNode(SourceLocation location) { return new AndOpNode(location); } }, OR(ImmutableList.of(OPERAND_0, SP, new Token("or"), SP, OPERAND_1), 2, LEFT) { @Override public OperatorNode createNode(SourceLocation location) { return new OrOpNode(location); } }, NULL_COALESCING(ImmutableList.of(OPERAND_0, SP, new Token("?:"), SP, OPERAND_1), 1, RIGHT) { @Override public OperatorNode createNode(SourceLocation location) { return new NullCoalescingOpNode(location); } }, CONDITIONAL( ImmutableList.of( OPERAND_0, SP, new Token("?"), SP, OPERAND_1, SP, new Token(":"), SP, OPERAND_2), 1, RIGHT) { @Override public OperatorNode createNode(SourceLocation location) { return new ConditionalOpNode(location); } }, ; /** Constants used in the enum definitions above. */ static class Constants { static final Spacer SP = new Spacer(); static final Operand OPERAND_0 = new Operand(0); static final Operand OPERAND_1 = new Operand(1); static final Operand OPERAND_2 = new Operand(2); } // ----------------------------------------------------------------------------------------------- /** Map used for fetching an Operator from the pair (tokenString, numOperands). */ private static final ImmutableTable<String, Integer, Operator> OPERATOR_TABLE; static { ImmutableTable.Builder<String, Integer, Operator> builder = ImmutableTable.builder(); for (Operator op : Operator.values()) { builder.put(op.getTokenString(), op.getNumOperands(), op); } OPERATOR_TABLE = builder.build(); } /** * Create an operator node, given the token, precedence, and list of children (arguments). * * @param op A string listing the operator token. If multiple tokens (e.g. the ternary conditional * operator), separate them using a space. * @param prec The precedence of an operator. Must match the precedence specified by {@code op}. * @param children The list of children (arguments) for the operator. * @return The matching OperatorNode. * @throws IllegalArgumentException If there is no Soy operator matching the given data. */ public static final OperatorNode createOperatorNode(String op, int prec, ExprNode... children) { checkArgument(OPERATOR_TABLE.containsRow(op)); Operator operator = OPERATOR_TABLE.get(op, children.length); if (operator.getPrecedence() != prec) { throw new IllegalArgumentException("invalid precedence " + prec + " for operator " + op); } return operator.createNode(children); } // ----------------------------------------------------------------------------------------------- /** The canonical syntax for this operator, including spacing. */ private final ImmutableList<SyntaxElement> syntax; /** * This operator's token. Multiple tokens (e.g. the ternary conditional operator) are separated * using a space. */ private final String tokenString; /** The number of operands that this operator takes. */ private final int numOperands; /** This operator's precedence level. */ private final int precedence; /** This operator's associativity. */ private final Associativity associativity; /** A short description of this operator (usually just the token string). */ private final String description; /** * Constructor that doesn't specify a description string (defaults to using the token string). * * @param syntax The canonical syntax for this operator, including spacing. * @param precedence This operator's precedence level. * @param associativity This operator's associativity. */ private Operator( ImmutableList<SyntaxElement> syntax, int precedence, Associativity associativity) { this(syntax, precedence, associativity, null /* description */); } /** * Constructor that specifies a description string. * * @param syntax The canonical syntax for this operator, including spacing. * @param precedence This operator's precedence level. * @param associativity This operator's associativity. * @param description A short description of this operator. */ private Operator( ImmutableList<SyntaxElement> syntax, int precedence, Associativity associativity, @Nullable String description) { this.syntax = syntax; String tokenString = null; int numOperands = 0; for (SyntaxElement syntaxEl : syntax) { if (syntaxEl instanceof Operand) { numOperands += 1; } else if (syntaxEl instanceof Token) { if (tokenString == null) { tokenString = ((Token) syntaxEl).getValue(); } else { tokenString += " " + ((Token) syntaxEl).getValue(); } } } checkArgument(tokenString != null && numOperands > 0); this.tokenString = tokenString; this.numOperands = numOperands; this.precedence = precedence; this.associativity = associativity; this.description = (description != null) ? description : tokenString; } /** Returns the canonical syntax for this operator, including spacing. */ public List<SyntaxElement> getSyntax() { return syntax; } /** * Returns this operator's token. Multiple tokens (e.g. the ternary conditional operator) are * separated using a space. */ public String getTokenString() { return tokenString; } /** Returns the number of operands that this operator takes. */ public int getNumOperands() { return numOperands; } /** Returns this operator's precedence level. */ public int getPrecedence() { return precedence; } /** Returns this operator's associativity. */ public Associativity getAssociativity() { return associativity; } /** Returns a short description of this operator (usually just the token string). */ public String getDescription() { return description; } /** Creates a node representing this operator. */ public abstract OperatorNode createNode(SourceLocation location); /** Creates a node representing this operator, with the given children. */ public final OperatorNode createNode(ExprNode... children) { checkArgument(children.length == getNumOperands()); // TODO(lukes): the source locations for all ExprNodes are pretty much a joke, currently all // ParentExprNodes just use the source location of their first child, so that is what we do here // but it is just wrong. OperatorNode node = createNode(children[0].getSourceLocation()); for (ExprNode child : children) { node.addChild(child); } return node; } // ----------------------------------------------------------------------------------------------- /** Enum for an operator's associativity. */ public static enum Associativity { /** Left-to-right. */ LEFT, /** Right-to-left. */ RIGHT } /** Represents a syntax element (used in a syntax specification for an operator). */ @Immutable public static interface SyntaxElement {} /** A syntax element for an operand. */ @Immutable public static class Operand implements SyntaxElement { private final int index; private Operand(int index) { this.index = index; } /** Returns the index of this operand. */ public int getIndex() { return index; } } /** A syntax element for a token. */ @Immutable public static class Token implements SyntaxElement { private final String value; private Token(String value) { this.value = value; } /** Returns this token's string literal. */ public String getValue() { return value; } } /** A syntax element for a space character. */ @Immutable public static class Spacer implements SyntaxElement { private Spacer() {} } }