!7

sclass Entry {
  S code, hints;
  transient F1<S, O> function;
  transient Throwable error;
  transient Map<S, O> cache = syncMRUCache(1000);
}

set flag DynModule.

cmodule FunctionsOnStrings > DynObjectTable<Entry> {
  transient ReliableSingleThread rstUpdateListLater = dm_rstWithDelay(this, r fireDataChanged, 10.0);
  
  start {
    itemToMap = func(Entry e) -> Map {
      litorderedmap(Code := e.code, Hints := nullIfEmpty(e.hints),
        Loaded := e.function != null,
        Error := strOrNull(e.error),
        "Cache Size" := l(e.cache) + "/" + mruCacheSize(e.cache))
    };
    dm_vmBus_onMessage_q('refreshedTranspiler, r refresh);
  }
  
  void refresh {
    lock lock;
    setData(new L);
  }
  
  // API
  
  F1 getFunction(S code, S hints) {
    lock lock;
    Entry e = objectWhere(data(), +code, +hints);
    if (e == null) add(e = nu Entry(+code, +hints));
    if (e.error != null) throw rethrow(e.error);
    if (e.function == null) {
      print("Loading function: " + code);
      try {
        final F1<S, O> f = codeWithHintsToFunctionOnString(code);
        final Map<S, O> cache = e.cache;
        e.function = func(S s) -> O {
          if (s == null) null;
          O o = cache.get(s);
          if (o == null) {
            cache.put(s, o = callF(f, s));
            rstUpdateListLater.trigger();
          }
          ret o;
        };
        fireDataChanged();
      } catch ex {
        e.error = ex;
        fireDataChanged();
        throw rethrow(ex);
      }
    }
    ret e.function;
  }
}