IO Operators

This commit is contained in:
Tibo De Peuter 2025-04-27 20:11:15 +02:00
parent b9f419a59d
commit 82a8fccf87
Signed by: tdpeuter
GPG key ID: 38297DE43F75FFE2
22 changed files with 450 additions and 199 deletions

View file

@ -1,6 +1,6 @@
package prolog
import Debug
import io.Logger
import prolog.ast.logic.Clause
import prolog.ast.logic.Predicate
import prolog.ast.logic.Resolvent
@ -16,13 +16,14 @@ object Program: Resolvent {
var predicates: Map<Functor, Predicate> = emptyMap()
init {
Logger.debug("Initializing ${this::class.java.simpleName}")
setup()
Logger.debug("Initialization of ${this::class.java.simpleName} complete")
}
private fun setup() {
if (Debug.on) {
println("Setting up Prolog program...")
}
Logger.debug("Setting up ${this::class.java.simpleName}")
// Initialize the program with built-in predicates
load(listOf(
))
@ -35,6 +36,7 @@ object Program: Resolvent {
fun query(goal: Goal): Answers = solve(goal, emptyMap())
override fun solve(goal: Goal, subs: Substitutions): Answers {
Logger.debug("Solving goal $goal")
val functor = goal.functor
// If the predicate does not exist, return false
val predicate = predicates[functor] ?: return emptySequence()
@ -57,6 +59,8 @@ object Program: Resolvent {
// If the predicate does not exist, create a new one
predicates += Pair(functor, Predicate(listOf(clause)))
}
Logger.debug("Loaded clause $clause into predicate $functor")
}
}
@ -74,6 +78,7 @@ object Program: Resolvent {
}
fun clear() {
Logger.debug("Clearing ${this::class.java.simpleName}")
predicates = emptyMap()
setup()
}

View file

@ -29,7 +29,6 @@ class Predicate : Resolvent {
*/
constructor(clauses: List<Clause>) {
this.functor = clauses.first().functor
require(clauses.all { it.functor == functor }) { "All clauses must have the same functor" }
this.clauses = clauses.toMutableList()
}
@ -39,11 +38,6 @@ class Predicate : Resolvent {
*/
fun add(clause: Clause) {
require(clause.functor == functor) { "Clause functor does not match predicate functor" }
if (Debug.on) {
println("Adding clause $clause to predicate $functor")
}
clauses.add(clause)
}

View file

@ -6,7 +6,7 @@ abstract class Operator(
private val symbol: Atom,
private val leftOperand: Operand? = null,
private val rightOperand: Operand
) : CompoundTerm(symbol, listOfNotNull(leftOperand, rightOperand)) {
) : CompoundTerm(symbol, listOfNotNull(leftOperand, rightOperand)), Term {
override fun toString(): String {
return when (leftOperand) {
null -> "${symbol.name} $rightOperand"

View file

@ -22,4 +22,15 @@ open class Structure(val name: Atom, var arguments: List<Argument>) : Goal(), He
else -> "${name.name}(${arguments.joinToString(", ")})"
}
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Structure) return false
if (functor != other.functor) return false
return arguments.zip(other.arguments).all { (a, b) -> a == b }
}
override fun hashCode(): Int {
return javaClass.hashCode()
}
}

View file

@ -1,5 +1,8 @@
package prolog.builtins
import io.Logger
import io.Terminal
import parser.ReplParser
import prolog.Answers
import prolog.Substitutions
import prolog.ast.logic.Satisfiable
@ -7,13 +10,60 @@ import prolog.ast.terms.Atom
import prolog.ast.terms.Operator
import prolog.ast.terms.Term
import prolog.logic.applySubstitution
import prolog.logic.unifyLazy
/**
* Write [Term] to the current output, using brackets and operators where appropriate.
*/
class Write(private val term: Term) : Operator(Atom("write"), null, term), Satisfiable {
override fun satisfy(subs: Substitutions): Answers {
val t = applySubstitution(term, subs)
println(t.toString())
Terminal().say(t.toString())
return sequenceOf(Result.success(emptyMap()))
}
}
}
/**
* Write a newline character to the current output stream.
*/
object Nl : Atom("nl"), Satisfiable {
override fun satisfy(subs: Substitutions): Answers {
Terminal().say("\n")
return sequenceOf(Result.success(emptyMap()))
}
}
/**
* Read the next Prolog term from the current input stream and unify it with [Term].
*
* On reaching end-of-file, [Term] is unified with the [Atom] `end_of_file`.
*/
class Read(private val term: Term) : Operator(Atom("read"), null, term), Satisfiable {
private val io = Terminal()
private val parser = ReplParser()
private fun readTerm(): Term {
val input = io.readLine()
Logger.debug("Read input: $input")
return when (input) {
"end_of_file" -> Atom("end_of_file")
else -> {
val out = parser.parse(input).query
Logger.debug("Parsed input: $out")
out as? Term ?: throw IllegalArgumentException("Expected a term, but got: $out")
}
}
}
override fun satisfy(subs: Substitutions): Answers = sequence {
val t1 = applySubstitution(term, subs)
val t2 = readTerm()
Logger.debug("Read term: $t2")
yieldAll(unifyLazy(t1, t2, subs))
}
}

View file

@ -6,6 +6,6 @@ import prolog.ast.logic.LogicOperand
import prolog.ast.terms.Atom
import prolog.ast.logic.LogicOperator
class Query(private val query: LogicOperand) : LogicOperator(Atom("?-"), null, query) {
class Query(val query: LogicOperand) : LogicOperator(Atom("?-"), null, query) {
override fun satisfy(subs: Substitutions): Answers = query.satisfy(subs)
}

View file

@ -14,21 +14,24 @@ import prolog.ast.arithmetic.Float
// Apply substitutions to a term
fun applySubstitution(term: Term, subs: Substitutions): Term = when {
variable(term, emptyMap()) -> subs[(term as Variable)] ?: term
atomic(term, subs) -> term
atomic(term, subs) -> term
compound(term, subs) -> {
val structure = term as Structure
Structure(structure.name, structure.arguments.map { applySubstitution(it, subs) })
}
else -> term
}
//TODO Combine with the other applySubstitution function
fun applySubstitution(expr: Expression, subs: Substitutions): Expression = when {
variable(expr, subs) -> applySubstitution(expr as Term, subs) as Expression
atomic(expr, subs) -> expr
atomic(expr, subs) -> expr
expr is LogicOperator -> {
expr.arguments = expr.arguments.map { applySubstitution(it, subs) }
expr
}
else -> expr
}
@ -40,6 +43,7 @@ private fun occurs(variable: Variable, term: Term, subs: Substitutions): Boolean
val structure = term as Structure
structure.arguments.any { occurs(variable, it, subs) }
}
else -> false
}
@ -56,12 +60,14 @@ fun unifyLazy(term1: Term, term2: Term, subs: Substitutions): Answers = sequence
yield(Result.success(subs + (variable to t2)))
}
}
variable(t2, subs) -> {
val variable = t2 as Variable
if (!occurs(variable, t1, subs)) {
yield(Result.success(subs + (variable to t1)))
}
}
compound(t1, subs) && compound(t2, subs) -> {
val structure1 = t1 as Structure
val structure2 = t2 as Structure
@ -75,14 +81,17 @@ fun unifyLazy(term1: Term, term2: Term, subs: Substitutions): Answers = sequence
}
// Combine the results of all unifications
val combinedResults = results.reduce { acc, result ->
acc.flatMap { a -> result.map { b ->
if (a.isSuccess && b.isSuccess) a.getOrThrow() + b.getOrThrow() else emptyMap()
} }.map { Result.success(it) }
acc.flatMap { a ->
result.map { b ->
if (a.isSuccess && b.isSuccess) a.getOrThrow() + b.getOrThrow() else emptyMap()
}
}.map { Result.success(it) }
}
yieldAll(combinedResults)
}
}
}
else -> {}
}
}
@ -122,37 +131,40 @@ fun compare(term1: Term, term2: Term, subs: Substitutions): Int {
return when (t1) {
is Variable -> {
when (t2) {
is Variable -> t1.name.compareTo(t2.name)
is Variable -> t1.name.compareTo(t2.name)
is Number -> -1
is Atom -> -1
is Atom -> -1
is Structure -> -1
else -> throw IllegalArgumentException("Cannot compare $t1 with $t2")
}
}
is Number -> {
when (t2) {
is Variable -> 1
is Integer -> (t1.value as Int).compareTo(t2.value)
is Float -> (t1.value as kotlin.Float).compareTo(t2.value)
is Atom -> -1
is Variable -> 1
is Integer -> (t1.value as Int).compareTo(t2.value)
is Float -> (t1.value as kotlin.Float).compareTo(t2.value)
is Atom -> -1
is Structure -> -1
else -> throw IllegalArgumentException("Cannot compare $t1 with $t2")
}
}
is Atom -> {
when (t2) {
is Variable -> 1
is Variable -> 1
is Number -> 1
is Atom -> t1.name.compareTo(t2.name)
is Atom -> t1.name.compareTo(t2.name)
is Structure -> -1
else -> throw IllegalArgumentException("Cannot compare $t1 with $t2")
}
}
is Structure -> {
when (t2) {
is Variable -> 1
is Number -> 1
is Atom -> 1
is Atom -> 1
is Structure -> {
val arityComparison = t1.arguments.size.compareTo(t2.arguments.size)
if (arityComparison != 0) return arityComparison
@ -164,9 +176,11 @@ fun compare(term1: Term, term2: Term, subs: Substitutions): Int {
}
return 0
}
else -> throw IllegalArgumentException("Cannot compare $t1 with $t2")
}
}
else -> throw IllegalArgumentException("Cannot compare $t1 with $t2")
}
}