Use the I/O library¶
How to load :io-lib into a Solver, configure its channels, and drive ISO stream/I/O predicates
programmatically. See I/O library for the full predicate catalogue and platform
caveats, and I/O library design for the rationale behind them.
1. Add the dependency¶
:io-lib depends on :solve and :parser-theory (see Module map); add it like any
other 2P-Kt module (see Add 2P-Kt as a dependency):
implementation("it.unibo.tuprolog:io-lib:2P_VERSION")
2. Load IOLib into a Solver¶
IOLib (it.unibo.tuprolog.solve.libs.io.IOLib) is a plain Library object — load it into a Runtime alongside
the standard builtins, either through SolverFactory.solverWithDefaultBuiltins:
import it.unibo.tuprolog.solve.Solver
import it.unibo.tuprolog.solve.library.Runtime
import it.unibo.tuprolog.solve.libs.io.IOLib
val solver = Solver.prolog.solverWithDefaultBuiltins(otherLibraries = Runtime.of(IOLib))
or through SolverBuilder for more fluent, stepwise construction (see
Solver API):
val solver =
Solver.prolog.newBuilder()
.runtime(Runtime.of(IOLib))
.build()
Inside the :dsl-solve DSL (see Prolog DSL), a MutableSolver's loadLibrary is
reachable directly on the scope:
import it.unibo.tuprolog.dsl.solve.logicProgramming
import it.unibo.tuprolog.solve.libs.io.IOLib
logicProgramming {
loadLibrary(IOLib)
// ... queries go here
}
3. Configure channels¶
open/3,4 create new named channels at runtime, but a solver's initial channels (standard input/output, or any
pre-opened alias you want a query to find already there) are set up when the solver is built. Use
SolverBuilder's input/output/standardInput/standardOutput/standardError (see
Solver API):
import it.unibo.tuprolog.solve.channel.InputChannel
import it.unibo.tuprolog.solve.channel.OutputChannel
val captured = StringBuilder()
val solver =
Solver.prolog.newBuilder()
.runtime(Runtime.of(IOLib))
.standardOutput(OutputChannel.of { captured.append(it) }) // redirect write/1, nl/1, ...
.input("greeting", InputChannel.of("hello")) // a pre-opened alias, readable via get_char(greeting, C)
.build()
Every channel :io-lib's predicates operate on — whether a solver default or one open/3,4 created — is a plain
InputChannel<String>/OutputChannel<String>; nothing in :io-lib depends on how a channel was built.
4. Open and use a stream¶
open/3,4 accepts both proper URLs and bare filesystem/http(s) paths as the SourceSink argument (via
Url.Companion.of). Building the query through the DSL (see Prolog DSL) reads much
closer to actual Prolog than assembling Structs by hand:
logicProgramming {
loadLibrary(IOLib)
val solution =
solveOnce(
"open"("theory.pl", "read", "Stream", logicListOf("alias"("src"))) and
("read_term"("src", "Term", logicListOf()) and "close"("src")),
)
}
Writing works the same way, in write/append mode:
logicProgramming {
loadLibrary(IOLib)
solveOnce(
"open"("out.txt", "write", "Stream", logicListOf("alias"("dst"))) and
("write"("dst", "hello") and "nl"("dst") and "close"("dst")),
)
}
See I/O library before relying on writing to anything other than a
local file, or on any predicate other than open/close/read_term/write-family/nl running the same way on
every platform — remote reads, in particular, stream lazily on the JVM but are fetched eagerly in full on JS.
5. Load a theory from a file or URL¶
consult/1 is the shortcut for "open, read every clause, close" — it accepts the same SourceSink shapes as
open/3,4:
logicProgramming {
loadLibrary(IOLib)
solveOnce("consult"("theory.pl"))
solveOnce("consult"("https://example.com/theory.pl"))
}
set_theory/1 is the same idea for an inline source string instead of a file/URL — useful when the theory text
is already in memory:
logicProgramming {
loadLibrary(IOLib)
solveOnce("set_theory"("parent(tom, bob). parent(bob, ann)."))
}
Unlike consult/1 (which appends to the current theory), set_theory/1 replaces the solver's static and
dynamic knowledge base, operators and flags outright.