1# Expressions 2 3As explained in [Expressions](/Expressions), Commander introduces an extensive expression system. This system is exposed as part of the API. On this page we will go over usage examples and a possible way to make expressions an optional integration. 4 5## Using expressions directly 6 7The built-in `Expression` class provides a string -> expression codec and a `parse` method. An expression is a `LootContext -> Expression.Result` function. `Expression.Result` represents a result value that can be converted to `BigDecimal`, `boolean`, `String`, `Instant`and `Duration`. 8 9To apply any of the expression functions you must have an instance of `LootContext`. 10 11```java 12public static final Expression EXP = Expression.parse("strFormat('%02.0f:%02.0f', floor((level.getDayTime / 1000 + 8) % 24), floor(60 * (level.getDayTime % 1000) / 1000))").result().orElseThrow(); 13 14public String worldTimeInHumanTime(LootContext context) { 15 return EXP.apply(context).getAsString(); 16} 17``` 18 19## Special functions 20 21Commander comes with `Arithmetica` and `BooleanExpression` which are `double` and `boolean` functions that can be encoded either as a constant or as an expression. It's worth noting that `"true"` will be decoded as an expression, but `true` will not. 22 23```json 242.2, "random(0, 3)" //Arithmetica 25 26true, "level.isDay" //BooleanExpression 27``` 28 29## Expressions as optional integration. 30 31When implementing support for Commander, you may want to make this integration optional. The easiest way to do this is to create a common interface that is then delegated to one of the implementations. This is how expressions are set-up in Andromeda's new config system. 32 33Let's start by defining a simple boolean intermediary interface. I recommend using a supplier as the parameter, so you don't construct a `LootContext` for constant values. 34 35```java 36public interface BooleanIntermediary { 37 boolean asBoolean(Supplier<LootContext> supplier); 38} 39``` 40 41Now let's implement the constant delegate. 42 43```java 44public record ConstantBooleanIntermediary(boolean value) implements BooleanIntermediary { 45 46 @Override 47 public boolean asBoolean(Supplier<LootContext> supplier) { 48 return this.value; 49 } 50} 51``` 52 53And our commander delegate. 54 55```java 56public final class CommanderBooleanIntermediary implements BooleanIntermediary { 57 58 private final BooleanExpression expression; 59 private final boolean constant; 60 61 public CommanderBooleanIntermediary(BooleanExpression expression) { 62 this.expression = expression; 63 this.constant = expression.toSource().left().isPresent(); //micro optimization 64 } 65 66 @Override 67 public boolean asBoolean(Supplier<LootContext> supplier) { 68 return this.expression.applyAsBoolean(constant ? null : supplier.get()); 69 } 70 71 public BooleanExpression getExpression() { 72 return this.expression; 73 } 74} 75``` 76 77With our delegates set-up we have to dynamically pick one or the other. Let's return to our intermediary interface and create a factory for constant values. Here I'm using `Support` from Dark Matter, but you can just copy this method: 78 79```java 80public static <T, F extends T, S extends T> T support(String mod, Supplier<F> expected, Supplier<S> fallback) { 81 return FabricLoader.getInstance().isModLoaded(mod) ? expected.get() : fallback.get(); 82} 83``` 84 85The method will check if Commander is installed and will pick the correct factory. 86 87```java 88public interface BooleanIntermediary { 89 Function<Boolean, BooleanIntermediary> FACTORY = Support.support("commander", 90 () -> b -> new CommanderBooleanIntermediary(BooleanExpression.constant(b)), 91 () -> ConstantBooleanIntermediary::new); 92 93 static BooleanIntermediary of(boolean value) { 94 return FACTORY.apply(value); 95 } 96 //... 97} 98``` 99 100Now we can define expressions in our config! 101 102```java 103public class Config { 104 public BooleanIntermediary value = BooleanIntermediary.of(true); 105} 106``` 107 108But wait, what about encoding/decoding? Let's actually get to that. Here we can create a codec for our delegates and use `support` to pick the correct one. 109 110```java 111public record ConstantBooleanIntermediary(boolean value) implements BooleanIntermediary { 112 public static final Codec<ConstantBooleanIntermediary> CODEC = Codec.BOOL.xmap(ConstantBooleanIntermediary::new, ConstantBooleanIntermediary::value); 113 //... 114} 115 116public final class CommanderBooleanIntermediary implements BooleanIntermediary { 117 public static final Codec<CommanderBooleanIntermediary> CODEC = BooleanExpression.CODEC.xmap(CommanderBooleanIntermediary::new, CommanderBooleanIntermediary::getExpression); 118 //... 119} 120``` 121 122Now we can use our codecs. 123 124```java 125Codec<BooleanIntermediary> codec = (Codec<BooleanIntermediary>) Support.fallback("commander", () -> CommanderBooleanIntermediary.CODEC, () -> ConstantBooleanIntermediary.CODEC); 126``` 127 128### Using intermediaries in configs. 129 130::: info Note 131 132This section only applies if you use Gson to read/write your configs. 133 134If you use a third-party library and the library doesn't allow you to pass a custom Gson instance, you could modify it using mixins. 135 136::: 137 138In Gson you can provide custom JsonSerializers/JsonDeserializers, which allows us to encode the intermediary using Codecs. 139 140Let's create our `CodecSerializer` class. 141 142::: details Code 143 144```java 145public record CodecSerializer<C>(Codec<C> codec) implements JsonSerializer<C>, JsonDeserializer<C> { 146 147 public static <C> CodecSerializer<C> of(Codec<C> codec) { 148 return new CodecSerializer<>(codec); 149 } 150 151 @Override 152 public C deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 153 var r = this.codec.parse(JsonOps.INSTANCE, json); 154 if (r.error().isPresent()) throw new JsonParseException(r.error().orElseThrow().message()); 155 return r.result().orElseThrow(); 156 } 157 158 @Override 159 public JsonElement serialize(C src, Type typeOfSrc, JsonSerializationContext context) { 160 var r = codec.encodeStart(JsonOps.INSTANCE, src); 161 if (r.error().isPresent()) throw new IllegalStateException(r.error().orElseThrow().message()); 162 return r.result().orElseThrow(); 163 } 164} 165``` 166 167::: 168 169And now we can register a type hierarchy adapter with our GsonBuilder. 170 171```java 172builder.registerTypeHierarchyAdapter(BooleanIntermediary.class, CodecSerializer.of(codec)); 173``` 174 175And we're done!