TL-Script can easily be extended with own functions. There are two mechanisms – pick the simplest one that fits the function:
com.top_logic.model.search.expr.config.operations.TLScriptFunctions. No registration in the application configuration is required.GenericMethod with a MethodBuilder and register it in the application configuration.The simplest way to add functions is to create a subclass of com.top_logic.model.search.expr.config.operations.TLScriptFunctions and add public static methods. Every public static method automatically becomes a TL-Script function – there is no registration in the application configuration, because all subclasses are discovered automatically at start-up. A subclass may live in any module that depends on com.top_logic.model.search.
@ScriptPrefix("math")
public class MathFunctions extends TLScriptFunctions {
/**
* Returns the absolute value of a number.
*
* @param a
* The value whose absolute value is to be determined.
* @return The absolute value of the argument.
*/
@Label("Absolute value of a number")
@SideEffectFree
public static double abs(@Mandatory double a) {
return Math.abs(a);
}
}
The function name is the prefix followed by the capitalized method name, joined with no separator (e.g. math + abs → mathAbs()). Always annotate the class with @ScriptPrefix to define this prefix: it groups the functions under a common namespace, prevents name conflicts with built-in or future functions, and lets users find them by prefix in the editor's auto-completion. Without the annotation the prefix defaults to the class's simple name, which is discouraged. The suffix of an individual function can be overridden with @Name on the method.
Each method parameter becomes a TL-Script parameter (positionally). Mark required parameters with @Mandatory; provide a default for a primitive parameter via the matching annotation (@StringDefault, @LongDefault, …); for a type that TL-Script cannot convert natively, annotate the parameter with @ScriptConversion naming a ValueConverter. The method's JavaDoc becomes the function description and each parameter is described by its @param tag; @Label overrides the generated UI label, and @SideEffectFree declares a side-effect-free function. Function names must be globally unique across all subclasses; a clash is reported as a configuration error at start-up. Functions defined this way need no separate documentation page – their description is generated from the JavaDoc.
The TL-Script function is implemented in a derivative of com.top_logic.model.search.expr.GenericMethod. The actual function is implemented in the overridden method com.top_logic.model.search.expr.Info.eval(Object, Object[], EvalContext). The method gets the self argument as first parameter and all other arguments in the arguments array. As a result, the method must return the function result of the TL-Script function.
Additionally, a builder for the function class must be created. This is created as a derivative of com.top_logic.model.search.expr.config.operations.AbstractSimpleMethodBuilder<I>. This builder creates an instance of the function implementation from above. The builder implementation is registered in the application configuration.
A minimal implementation of a TL-Script function that rounds off a number might look like this:
public class Floor extends SimpleGenericMethod {
protected Floor(String name, SearchExpression self, SearchExpression[] arguments) {
super(name, self, arguments);
}
@Override
public GenericMethod copy(SearchExpression self, SearchExpression[] arguments) {
return new Floor(getName(), self, arguments);
}
@Override
public TLType getType(TLType selfType, List<TLType> argumentTypes) {
return selfType;
}
@Override
public Object eval(Object self, Object[] arguments) {
return Math.floor(asDouble(self));
}
public static final class Builder extends AbstractSimpleMethodBuilder<Floor> {
public Builder(InstantiationContext context, Config<?> config) {
super(context, config);
}
@Override
public Floor build(Expr expr, SearchExpression self, SearchExpression[] args)
throws ConfigurationException {
return new Floor(getConfig().getName(), self, args);
}
}
}
If the function takes arguments, describe them with a constant ArgumentDescriptor by overriding descriptor() in the builder. This enables calling the function with named arguments (e.g. my_fun(value: 42)), documents the signature and validates the number of arguments.
private static final ArgumentDescriptor DESCRIPTOR = ArgumentDescriptor.builder()
.mandatory("value")
// .optional("digits", 0)
.build();
@Override
public ArgumentDescriptor descriptor() {
return DESCRIPTOR;
}
Note: If a single expression class is reused for several registrations (parameterized by the builder configuration), override getId() on both the builder and the expression and return a value that is unique across all builders (e.g. the pair of the expression class and a configured discriminator). Otherwise the compiled form of the expression may be re-created by the wrong builder.
The builder for the function implementation is registered in the application configuration in the section com.top_logic.model.search.expr.config.SearchBuilder. The above function could be registered as my_floor as follows:
<config service-class="com.top_logic.model.search.expr.config.SearchBuilder">
<instance>
<methods>
<method name="my_floor" class="my.package.Floor$Builder"/>
</methods>
</instance>
</config>
Like built-in functions, custom functions can be called using the name assigned in the configuration. To prevent name conflicts with future updates, it is recommended to use a name prefix like my_ in the configuration example above.
The function registered above can then be called as follows:
my_floor(4.2)
The expected result would then be 4.
This applies to functions registered via a MethodBuilder; functions defined as static methods on a TLScriptFunctions subclass are documented through their JavaDoc, as described above.
A MethodBuilder-based TL-Script function is only complete once it is documented – otherwise users cannot discover or understand it. Provide a documentation page for every such function. Built-in engine functions are documented under src/main/webapp/doc/{en,de}/DeveloperGuide/TLScript/<Section>/<functionName>/, with one directory per function (named exactly like the registered <method name>) containing:
index.html – an HTML fragment with the sections Syntax, Description, Parameters (a tlDocTable), Return value and Examples.page.properties – with position (ordering within the section), source, title and a uuid (identical for the en and de variant).Pages are discovered by directory, so there is no central index to maintain. Copy an existing page as a template.