diff --git a/documentation/reference/coercions.md b/documentation/reference/coercions.md index 03fe19e2f2..db67c0f635 100644 --- a/documentation/reference/coercions.md +++ b/documentation/reference/coercions.md @@ -36,6 +36,7 @@ There are a few ways to coerce a value to a boolean. * use it in an [if-expression](control-flow#if) * use it in a [ternary expression](expressions#ternary-operator) +* use the built-in function [`Boolean()`](functions#Boolean) All values have a boolean coercion (sometimes referred to as a 'truthiness' check), these mostly follow JavaScript semantics: diff --git a/documentation/reference/functions.md b/documentation/reference/functions.md index b6e0c9cdb5..8b34d5ad1d 100644 --- a/documentation/reference/functions.md +++ b/documentation/reference/functions.md @@ -13,6 +13,10 @@ custom external functions, see ## Basic Functions +### `Boolean(value)` {#Boolean} + +Explicitly coerces the argument to a boolean. + ### `checkNotNull(value)` {#checkNotNull} Throws a runtime exception if the given value is `null` and returns the value diff --git a/java/src/com/google/template/soy/basicfunctions/BasicFunctions.java b/java/src/com/google/template/soy/basicfunctions/BasicFunctions.java index 7595ae7adc..6664bb4d92 100644 --- a/java/src/com/google/template/soy/basicfunctions/BasicFunctions.java +++ b/java/src/com/google/template/soy/basicfunctions/BasicFunctions.java @@ -27,6 +27,7 @@ private BasicFunctions() {} public static ImmutableList functions() { return ImmutableList.of( // go/keep-sorted start + new BooleanFunction(), new CeilingFunction(), new ConcatListsFunction(), new ConcatMapsMethod(), diff --git a/java/src/com/google/template/soy/basicfunctions/BasicFunctionsRuntime.java b/java/src/com/google/template/soy/basicfunctions/BasicFunctionsRuntime.java index ba2c5ac218..92132c3219 100644 --- a/java/src/com/google/template/soy/basicfunctions/BasicFunctionsRuntime.java +++ b/java/src/com/google/template/soy/basicfunctions/BasicFunctionsRuntime.java @@ -488,4 +488,8 @@ private static int clampStrIndex(String str, NumberData position) { public static boolean isFinite(SoyValue arg) { return arg instanceof NumberData && Double.isFinite(arg.numberValue()); } + + public static boolean booleanFunc(SoyValue value) { + return value != null && value.coerceToBoolean(); + } } diff --git a/java/src/com/google/template/soy/basicfunctions/BooleanFunction.java b/java/src/com/google/template/soy/basicfunctions/BooleanFunction.java new file mode 100644 index 0000000000..78fef1f294 --- /dev/null +++ b/java/src/com/google/template/soy/basicfunctions/BooleanFunction.java @@ -0,0 +1,72 @@ +/* + * Copyright 2009 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.basicfunctions; + +import com.google.template.soy.data.SoyValue; +import com.google.template.soy.plugin.java.restricted.JavaPluginContext; +import com.google.template.soy.plugin.java.restricted.JavaValue; +import com.google.template.soy.plugin.java.restricted.JavaValueFactory; +import com.google.template.soy.plugin.java.restricted.SoyJavaSourceFunction; +import com.google.template.soy.plugin.javascript.restricted.JavaScriptPluginContext; +import com.google.template.soy.plugin.javascript.restricted.JavaScriptValue; +import com.google.template.soy.plugin.javascript.restricted.JavaScriptValueFactory; +import com.google.template.soy.plugin.javascript.restricted.SoyJavaScriptSourceFunction; +import com.google.template.soy.plugin.python.restricted.PythonPluginContext; +import com.google.template.soy.plugin.python.restricted.PythonValue; +import com.google.template.soy.plugin.python.restricted.PythonValueFactory; +import com.google.template.soy.plugin.python.restricted.SoyPythonSourceFunction; +import com.google.template.soy.shared.restricted.Signature; +import com.google.template.soy.shared.restricted.SoyFunctionSignature; +import com.google.template.soy.shared.restricted.SoyPureFunction; +import java.lang.reflect.Method; +import java.util.List; + +/** Soy function that takes the ceiling of a number. */ +@SoyPureFunction +@SoyFunctionSignature( + name = "Boolean", + value = + @Signature( + parameterTypes = {"?"}, + returnType = "bool")) +final class BooleanFunction + implements SoyJavaSourceFunction, SoyJavaScriptSourceFunction, SoyPythonSourceFunction { + + @Override + public JavaScriptValue applyForJavaScriptSource( + JavaScriptValueFactory factory, List args, JavaScriptPluginContext context) { + return factory.global("Boolean").invoke(args.get(0)); + } + + @Override + public PythonValue applyForPythonSource( + PythonValueFactory factory, List args, PythonPluginContext context) { + return factory.global("bool").call(args.get(0)); + } + + // lazy singleton pattern, allows other backends to avoid the work. + private static final class Methods { + static final Method BOOLEAN_FN = + JavaValueFactory.createMethod(BasicFunctionsRuntime.class, "booleanFunc", SoyValue.class); + } + + @Override + public JavaValue applyForJavaSource( + JavaValueFactory factory, List args, JavaPluginContext context) { + return factory.callStaticMethod(Methods.BOOLEAN_FN, args.get(0)); + } +} diff --git a/java/src/com/google/template/soy/jssrc/internal/JavaScriptValueFactoryImpl.java b/java/src/com/google/template/soy/jssrc/internal/JavaScriptValueFactoryImpl.java index 3820117f0b..57b4754199 100644 --- a/java/src/com/google/template/soy/jssrc/internal/JavaScriptValueFactoryImpl.java +++ b/java/src/com/google/template/soy/jssrc/internal/JavaScriptValueFactoryImpl.java @@ -272,6 +272,11 @@ public JavaScriptValueImpl invokeMethod(String methodName, JavaScriptValue... ar impl.dotAccess(methodName).call(unwrapParams(Arrays.asList(args)))); } + @Override + public JavaScriptValueImpl invoke(JavaScriptValue... args) { + return new JavaScriptValueImpl(impl.call(unwrapParams(Arrays.asList(args)))); + } + @Override public JavaScriptValueImpl accessProperty(String ident) { return new JavaScriptValueImpl(impl.dotAccess(ident)); @@ -281,5 +286,6 @@ public JavaScriptValueImpl accessProperty(String ident) { public String toString() { return impl.getCode(FormatOptions.JSSRC); } + } } diff --git a/java/src/com/google/template/soy/plugin/javascript/restricted/JavaScriptValue.java b/java/src/com/google/template/soy/plugin/javascript/restricted/JavaScriptValue.java index 49a0aee82b..fb9042842c 100644 --- a/java/src/com/google/template/soy/plugin/javascript/restricted/JavaScriptValue.java +++ b/java/src/com/google/template/soy/plugin/javascript/restricted/JavaScriptValue.java @@ -48,6 +48,10 @@ default JavaScriptValue invokeMethod(String ident, Iterable arg return invokeMethod(ident, Iterables.toArray(args, JavaScriptValue.class)); } + /** Generates a call on the symbol. */ + JavaScriptValue invoke(JavaScriptValue... args); + /** Accesses a property on the given object. */ JavaScriptValue accessProperty(String ident); + }