/* * Copyright 2015 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.shared.internal; import com.google.template.soy.data.SanitizedContent; import com.google.template.soy.data.SoyValue; import com.google.template.soy.data.restricted.FloatData; import com.google.template.soy.data.restricted.IntegerData; import com.google.template.soy.data.restricted.NumberData; import com.google.template.soy.data.restricted.StringData; import java.util.Objects; /** * Runtime implementation of common expression operators to be shared between the {@code jbcsrc} and * {@code Tofu} backends. */ public final class SharedRuntime { /** * Custom equality operator that smooths out differences between different Soy runtimes. * * <p>This approximates Javascript's behavior, but is much easier to understand. */ public static boolean equal(SoyValue operand0, SoyValue operand1) { // Treat the case where either is a string specially. // TODO(gboyer): This should probably handle SanitizedContent == SanitizedContent, even though // Javascript doesn't handle that case properly. http://b/21461181 if (operand0 instanceof StringData) { return compareString(operand0.stringValue(), operand1); } if (operand1 instanceof StringData) { return compareString(operand1.stringValue(), operand0); } return Objects.equals(operand0, operand1); } /** Performs the {@code +} operator on the two values. */ public static SoyValue plus(SoyValue operand0, SoyValue operand1) { if (operand0 instanceof IntegerData && operand1 instanceof IntegerData) { return IntegerData.forValue(operand0.longValue() + operand1.longValue()); } else if (operand0 instanceof NumberData && operand1 instanceof NumberData) { return FloatData.forValue(operand0.numberValue() + operand1.numberValue()); } else { // String concatenation is the fallback for other types (like in JS). Use the implemented // coerceToString() for the type. return StringData.forValue(operand0.coerceToString() + operand1.coerceToString()); } } /** Performs the {@code -} operator on the two values. */ public static SoyValue minus(SoyValue operand0, SoyValue operand1) { if (operand0 instanceof IntegerData && operand1 instanceof IntegerData) { return IntegerData.forValue(operand0.longValue() - operand1.longValue()); } else { return FloatData.forValue(operand0.numberValue() - operand1.numberValue()); } } /** Performs the {@code *} operator on the two values. */ public static NumberData times(SoyValue operand0, SoyValue operand1) { if (operand0 instanceof IntegerData && operand1 instanceof IntegerData) { return IntegerData.forValue(operand0.longValue() * operand1.longValue()); } else { return FloatData.forValue(operand0.numberValue() * operand1.numberValue()); } } /** Performs the {@code /} operator on the two values. */ public static double dividedBy(SoyValue operand0, SoyValue operand1) { // Note: Soy always performs floating-point division, even on two integers (like JavaScript). // Note that this *will* lose precision for longs. return operand0.numberValue() / operand1.numberValue(); } /** Performs the {@code <} operator on the two values. */ public static boolean lessThan(SoyValue operand0, SoyValue operand1) { if (operand0 instanceof IntegerData && operand1 instanceof IntegerData) { return operand0.longValue() < operand1.longValue(); } else { return operand0.numberValue() < operand1.numberValue(); } } /** Performs the {@code <=} operator on the two values. */ public static boolean lessThanOrEqual(SoyValue operand0, SoyValue operand1) { if (operand0 instanceof IntegerData && operand1 instanceof IntegerData) { return operand0.longValue() <= operand1.longValue(); } else { return operand0.numberValue() <= operand1.numberValue(); } } /** Performs the unary negation {@code -} operator on the value. */ public static NumberData negative(SoyValue node) { if (node instanceof IntegerData) { return IntegerData.forValue(-node.longValue()); } else { return FloatData.forValue(-node.floatValue()); } } /** Determines if the operand's string form can be equality-compared with a string. */ public static boolean compareString(String string, SoyValue other) { // This follows similarly to the Javascript specification, to ensure similar operation // over Javascript and Java: http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.3 if (other instanceof StringData || other instanceof SanitizedContent) { return string.equals(other.toString()); } if (other instanceof NumberData) { try { // Parse the string as a number. return Double.parseDouble(string) == other.numberValue(); } catch (NumberFormatException nfe) { // Didn't parse as a number. return false; } } return false; } private SharedRuntime() {} }