Own extensions
TL-Script can easily be extended with own functions. There are two mechanisms – pick the simplest one that fits the function:
- Simple functions that map directly to a Java static method: add a static method to a subclass of
com.top_logic.model.search.expr.config.operations.TLScriptFunctions. No registration in the application configuration is required. - Functions with full control over argument evaluation, the computed result type or lazy/context-dependent evaluation: implement a
GenericMethodwith aMethodBuilderand register it in the application configuration.
Simple functions via static methods
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.
Functions with full control
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);
}
}
}
Naming the arguments
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.
Configuration
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>
Usage
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.
Documentation
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 (atlDocTable), Return value and Examples.page.properties– withposition(ordering within the section),source,titleand auuid(identical for theenanddevariant).
Pages are discovered by directory, so there is no central index to maintain. Copy an existing page as a template.