Skip to content

Getting started with 2P-Kt in JavaScript

This tutorial takes you from an empty Node.js project to a working Prolog knowledge base: you'll declare a small family tree, ask who Abraham is an ancestor of, and understand the answers 2P-Kt gives back.

2P-Kt is written in Kotlin and compiled to JavaScript via Kotlin/JS. Most of its API keeps its Kotlin package structure in the compiled output rather than being flattened for JS, and member names are kept stable with @JsName (Kotlin doesn't allow overloading in JS, so without it names like solve would come out mangled). One part of the API is flattened and exported cleanly for direct JS/TS use: the concrete solver factories (ClassicSolverFactory and friends), each annotated @JsExport for exactly this purpose — this tutorial builds around that entry point rather than the Solver.prolog shortcut you'd use from Kotlin or Java (which resolves the classic engine reflectively, a mechanism aimed at same-platform Kotlin/JVM code rather than plain JS).

1. Add the dependency

You need solve-classic (the SLD-resolution engine) and parser-theory (to read Prolog source text into a Theory):

{
  "dependencies": {
    "@tuprolog/solve-classic": "^2P_VERSION",
    "@tuprolog/parser-theory": "^2P_VERSION"
  }
}

See Add 2P-Kt as a dependency for background on the npm packages.

2. Write the knowledge base as Prolog text

The simplest way to get a Theory is to write it as ordinary Prolog source and parse it with ClausesParser:

const { it: coreIt } = require("@tuprolog/parser-theory");
const { ClausesParser } = coreIt.unibo.tuprolog.theory.parsing;

const source = `
parent(abraham, isaac).
parent(isaac, jacob).
parent(jacob, joseph).
ancestor(X, Y) :- parent(X, Y).
ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y).
`;

const theory = ClausesParser.withDefaultOperators().parseTheory(source);

Note the nested it.unibo.tuprolog... path: since ClausesParser isn't one of the explicitly-@JsExported types, it surfaces under a namespace object mirroring its original Kotlin package rather than as a flat named export. If a given 2P-Kt release changes this, your bundler/editor's autocomplete on the required module — or the package's shipped .d.ts file — will show you the current shape.

3. Build a solver

ClassicSolverFactory is exported directly and cleanly — no nested path needed. It exposes the same SolverFactory builder used across every 2P-Kt host language:

const { ClassicSolverFactory } = require("@tuprolog/solve-classic");

const solver = ClassicSolverFactory.newBuilder()
  .staticKb(theory)
  .buildMutable();

Default predicates (is/2, comparisons, list built-ins, ...) are loaded automatically unless you call .noBuiltins() on the builder first.

4. Build the query

const { Struct, Atom, Var } = coreIt.unibo.tuprolog.core;

const query = Struct.of("ancestor", Atom.of("abraham"), Var.of("X"));

(@tuprolog/parser-theory depends on @tuprolog/core and re-exposes it transitively as it.unibo.tuprolog.core on the same required module — or require("@tuprolog/core") directly if you'd rather keep the two separate.)

5. Run it and read the solutions

solveList(goal) eagerly collects every solution; Kotlin's List compiles to a JS array-like object you can iterate with a plain for...of:

const solutions = solver.solveList(query);
for (const solution of solutions) {
  if (solution.isYes) {
    console.log(solution.substitution.getByName("X"));
  }
}

Running this logs isaac, jacob and joseph — one line per way ancestor(abraham, X) can be proven true. solution.isYes reads as a plain property here (Kotlin/JS compiles a Kotlin val to a native JS getter, unlike the JVM/Java build where the same property becomes a method call, isYes()); Solution.No and Solution.Halt are the other two cases a solution can be in — see Solver API.

Next steps

  • Solver API documents Solver, SolveOptions (timeouts, solution limits, eager vs. lazy) and Library/Runtime in full.
  • Term hierarchy and Default predicates cover the rest of the term-construction and standard-library surface — every type there follows the same nested-namespace convention shown above unless it's one of the explicitly-exported solver factories.
  • How-to guides has task-oriented recipes once you're past the basics.