java.lang.Object
com.darkcollective.relix.function.FunctionCatalog

public final class FunctionCatalog extends Object
Every function available to a run, indexed by name.

The catalogue is built once — from the installed libraries — and then only read. Building it once matters: two catalogues assembled separately can disagree about what a name means, and the disagreement shows up as analysis accepting a call that evaluation cannot make. Discover once, and pass the result along with the rest of the analysis.


 FunctionCatalog catalog = FunctionCatalog.discover();
 Optional<ScalarFunction> upper = catalog.scalar("UCase");
 

Lookup is case-insensitive throughout: UCase, ucase and UCASE are one function. When two libraries offer the same name the higher priority wins, and the loss is logged rather than passed over — a replaced built-in should be a decision, not a surprise.

Holding a function here does not put it in a symbol table. The engine registers a function on first reference, so a script's symbol table — and the IR dump printed from it — lists the functions that script actually calls rather than the whole library. This catalogue is the thing that answers "is there such a function, and what is its form"; registration stays a consequence of a script mentioning the name.

  • Method Details

    • discover

      public static FunctionCatalog discover()
      Builds a catalogue from every FunctionLibrary installed on the module path or the class path.

      Discovery is declared here rather than by each consumer, because ServiceLoader resolves services against the module that calls it: doing it in one place means no consumer needs a service declaration of its own, and there is one catalogue rather than several.

      Returns:
      a catalogue over the discovered libraries; empty when none is installed
    • discoverWith

      public static FunctionCatalog discoverWith(Collection<FunctionLibrary> extra)
      Builds a catalogue over the discovered libraries plus the given ones.

      For a program that installs a library of its own without publishing it as a service. The SPI's claim is that anything the shipped library can do a third party can do; requiring a META-INF/services entry to exercise that inside one's own program would be a gap in the claim rather than a feature of it.

      Discovery stays here, for the reason discover() gives — a caller enumerating services itself would need a uses declaration of its own, and two independently-populated catalogues can disagree about what a name means.

      Clashes are resolved by FunctionLibrary.priority() exactly as they are among discovered libraries, so an extra library must declare a higher priority than the bundled 0 to replace a built-in rather than merely to add to it.

      Parameters:
      extra - libraries to index alongside the discovered ones; must not be null
      Returns:
      a catalogue over both
    • of

      public static FunctionCatalog of(FunctionLibrary... libraries)
      Builds a catalogue over exactly these libraries, bypassing discovery.
      Parameters:
      libraries - the libraries to index
      Returns:
      a catalogue over libraries
    • of

      public static FunctionCatalog of(Collection<FunctionLibrary> libraries)
      Builds a catalogue over exactly these libraries, bypassing discovery.
      Parameters:
      libraries - the libraries to index
      Returns:
      a catalogue over libraries
    • empty

      public static FunctionCatalog empty()
      The catalogue holding nothing — useful where a caller must supply one and the functions are irrelevant.
      Returns:
      an empty catalogue
    • scalar

      public Optional<ScalarFunction> scalar(String name)
      Looks up a scalar function by name, case-insensitively.
      Parameters:
      name - the function name as written at the call site
      Returns:
      the function, or empty when no library offers that name
    • aggregate

      public Optional<AggregateFunction> aggregate(String name)
      Looks up an aggregate by name, case-insensitively.
      Parameters:
      name - the aggregate name as written at the call site
      Returns:
      the aggregate, or empty when no library offers that name
    • resolve

      public Optional<ScalarFunction> resolve(String name, List<ScalarType> argumentTypes)
      Resolves a scalar call against this catalogue.

      A name identifies one function, so the argument types are accepted and not consulted; the parameter is here because resolution by argument type is a property of the lookup rather than of a caller, and a caller written against this form keeps working if the rule ever sharpens. To ask what a call returns, take the resolved function's ScalarFunction.returnTypeFor(List).

      Parameters:
      name - the function name as written at the call site
      argumentTypes - the argument types at that call site, in order
      Returns:
      the function, or empty when no library offers that name
    • libraries

      public List<FunctionLibrary> libraries()
      The libraries this catalogue was built over, in claiming order.

      Which implementations are actually installed is otherwise unanswerable from inside a running program — discovery is a ServiceLoader scan, and a missing provider fails nothing a compiler can see. This is what lets a process report its own inventory.

      Returns:
      the libraries, highest priority first; never null
    • scalars

      public Collection<ScalarFunction> scalars()
    • aggregates

      public Collection<AggregateFunction> aggregates()
      Returns:
      every aggregate in the catalogue, in the order the libraries offered them
    • names

      public Set<String> names()
      The canonical spelling of every name in the catalogue — scalar functions first, then aggregates. These are display forms; lookup is case-insensitive.
      Returns:
      the declared names
    • documentation

      public Optional<String> documentation(String docKey)
      The documentation page a library holds under docKey, as Markdown.

      Asked of the libraries in the order they get to claim a name, so the library whose function a caller resolved is the one that answers for it. The engine never reads a library's resources itself — see FunctionLibrary.documentation(String) for why that is the seam.

      Parameters:
      docKey - the key a FunctionSignature.docKey() declared
      Returns:
      the Markdown page, or empty when no installed library has one
    • isEmpty

      public boolean isEmpty()
      Returns:
      true when no library offered a single function — which, outside a deliberately empty() catalogue, means no library was found