For the next version of FreshMarker, a number of new options will be created for the template engine. These new possibilities include the Spaceship Operator and a new String Concatenation Operator.
The Spaceship Operator is a comparison operator that is used in various programming languages to calculate three different comparison results in a single step. It is often used by the symbol <=>
, which looks like a small spaceship, which gives the operator its name.
By using the Spaceship Operator, the coding of comparisons becomes shorter and more elegant, especially in situations where all three results (smaller, equal, larger) are required.
The Spaceship Operator is helpful for FreshMarker because it reduces the size of the templates.
<#if datum < .now?date> Der Termin liegt in der Vergangeheit <#elseif datum == .now?date> Der Termin ist heute <#else> Der Termin liegt in der Zukunft </#if>
This template produces different outputs depending on whether the date in the variable datum
is in the past or in the future. To do this, the variable is compared twice with the current date in .now?date
.
A variant with the Spaceship Operator is somewhat simpler.
<#switch datum <=> .now?date> <#on -1>Der Termin liegt in der Vergangeheit <#on 0>Der Termin ist heute <#on 1>Der Termin liegt in der Zukunft </#switch>
The Spaceship Operator is only called once and the result is used in a Switch Directive.
The Spaceship Operator is relatively easy to implement because, like the relational operators, it is based on the comparison properties of the base types. Most of them either implement the Comparable
interface or have similiar functionalities.
protected TemplatePrimitive<?> compareValues(TokenType operator, int compare) { if (TokenType.COMPARE == operator) { return TemplateNumber.of(Integer.signum(compare)); } return TemplateBoolean.from(switch (operator) { case LT -> compare < 0; case GT -> compare > 0; case LTE -> compare <= 0; case GTE, UNICODE_GTE -> compare >= 0; default -> throw new ProcessException("unsupported operation: " + operator); }); }
The TemplatePrimitive
base class provides the compareValues
method, which uses an operator
and the compare
value to generate the matching return value. For the relational operators, the compare
value is compared with zero. For the COMPARE
operator, our Spaceship Operator, the compare value is returned directly as a TemplateNumber
.
@Override public TemplatePrimitive<?> relational(TokenType operator, TemplatePrimitive<?> operand, ProcessContext context) { TemplateString rightValue = (TemplateString) operand; return compareValues(operator, getValue().compareTo(rightValue.getValue())); }
The compareValue
method is used by the relational
methods in their subclasses. This example is the relational
method of the TemlateString
class. Here the compare
value is calculated as the result of the String#compareTo
method and passed to the compareValue
method. With this implementation, all primitive FreshMarker types can use the Spaceship Operator if they also support the relational operators.
The second operator is a new String Concatenation Operator. The previous String Concatenation Operator +
concatenates two strings, regardless of whether they are string literals or string values in variables.
${hello + ' ' + world}
In this example, the variables hello
and world
contain the corresponding strings and therefore the above interpolation results in the value Hello World
. It happens relatively often, as in this example, that the strings are concatenated with spaces.
Of course, it would be nice if you could avoid the explicit concatenation with spaces. This is where the new String Concatenation Operator comes into play. This implicitly inserts a space between the strings to be concatenated.
${hello ~ world}
This example uses the new operator and you can see that no space needs to be inserted. This happens behind the scenes in the TemplateString
class.
public TemplateString concat(TemplateString other, String seperator) { if (getValue().isEmpty()) { return other; } if (other.getValue().isEmpty()) { return this; } return new TemplateString(getValue() + seperator + other.getValue()); } @Override public TemplateObject operation(TokenType operator, TemplateObject operand, ProcessContext context) { return switch (operator) { case PLUS -> concat(operand.evaluate(context, TemplateString.class), ""); case CONCAT -> concat(operand.evaluate(context, TemplateString.class), " "); default -> super.operation(operator, operand, context); }; }
The operation
method is used for both operators. Both call the internal method concat
. The difference between the two calls is the separator, which is once the empty string and once a blank character. This completes the implementation of the two new operators, and all that remains is to wait for FreshMarker 2.0.0.