Package org.kink_lang.kink.hostfun


package org.kink_lang.kink.hostfun
Interfaces used to make host system functions, or host funs, which are Kink functions defined in Java code.

Host funs can be built using HostFunBuilder, through vm.fun.make() or vm.fun.make(desc).

The following example makes a host fun which compares two nums then returns the smaller one.

  FunVal min = vm.fun.make("min(x y)").take(2).action(c -> {
      Val x = c.arg(0);
      Val y = c.arg(1);
      if (! (x instanceof NumVal && y instanceof NumVal)) {
          return c.raise("max(x y): required two nums, but were not");
      }
      BigDecimal xn = ((NumVal) x).bigDecimal();
      BigDecimal yn = ((NumVal) y).bigDecimal();
      return xn.compareTo(yn) <= 0 ? x : y;
  });
 

If the number of arguments passed to the fun is not two, the fun raises a Kink exception, because take(2) is specified. After the arity checking, the control is delegated to the lambda given to action method.

The following is a quick guidance to compose a host fun:

Take a recv and args

You can get the recv passed to the fun call using CallContext.recv().

You can get the number of args using CallContext.argCount(), and get an arg using CallContext.arg(int).

Return a val

If you want the fun to return a val, just return the val as the result of the lambda, as in the example program above.

Raise an exception

If you want to raise an exception, retrun the result of HostContext.raise(String) or HostContext.raise(Throwable) as in the example program above.

Call a fun

You can call a fun as both a tail call and a non-tail call.

If you want to call a fun as a tail call, use flowish API starting from HostContext.call methods without terminating by CallFlowToOn.on(HostFunReaction).

Example:

  int exitHandle = vm.sym.handleFor("exit");
  FunVal abort = vm.fun.make().action(c -> {
      return c.call("kink/PROCESS", exitHandle).args(vm.num.of(1));
  });
 

If you want to call a fun as a non-tail call, use flowish API starting from HostContext.call methods terminating by CallFlowToOn.on(HostFunReaction).

Example:

  int opLtHandle = vm.sym.handleFor("op_lt");
  FunVal min = vm.fun.make("min(x y)").take(2).action(c -> {
      Val x = c.arg(0);
      Val y = c.arg(1);
      return c.call(y, opLtHandle).args(x).on((cc, yIsSmaller) -> {
          // yIsSmaller is the result of y.op_lt(x)
          return yIsSmaller.equals(vm.bool.trueVal) ? y
              : yIsSmaller.equals(vm.bool.falseVal) ? x
              : cc.raise("min(x y): got non-bool result from < operator");
      });
  });
 

CallContext given to the action lambda (c in the program above) is valid until the lambda returns the result. Thus, in the on lambda, HostContext given to the on lambda (cc in the program above) must be used instead of c.

Note: the Kink runtime manages its own runtime. Thus, an action fun returns a HostResult to the Kink runtime to let it call a Kink fun, instead of calling the Kink fun on the Java stack frame of the action lambda itself. This kind of technique is called “trampoline.”

Call a graph

Trampoline code described in the previous section can become messy easily, when calling multiple functions. In this case, you can use Graph DSL. Graph DSL abstracts out a chain of CallFlowToOn.on(ThrowingFunction2).

Example:

  FunVal min = vm.fun.make("min(x y)").take(2).action(c -> {
      Val x = c.arg(0);
      Val y = c.arg(1);
      if (! (x instanceof NumVal && y instanceof NumVal)) {
          // call a graph node
          return c.call(vm.graph.raiseFormat(
              "min(x y): required two nums, but got {} and {}",
              vm.graph.repr(x),
              vm.graph.repr(y)));
      }
      BigDecimal xn = ((NumVal) x).bigDecimal();
      BigDecimal yn = ((NumVal) y).bigDecimal();
      return xn.compareTo(yn) <= 0 ? x : y;
  });