Checkpoint

This commit is contained in:
Tibo De Peuter 2025-05-01 17:13:35 +02:00
parent 43b364044e
commit 9db1c66781
Signed by: tdpeuter
GPG key ID: 38297DE43F75FFE2
34 changed files with 746 additions and 194 deletions

View file

@ -5,7 +5,7 @@ import io.GhentPrologArgParser
import io.Logger
import repl.Repl
fun main(args: Array<String>) = mainBody {
fun main(args: Array<String>) {
// Parse command line arguments
val parsedArgs = ArgParser(args).parseInto(::GhentPrologArgParser)
@ -22,21 +22,9 @@ fun main(args: Array<String>) = mainBody {
// Check if REPL was requested
if (repl) {
Repl().start()
Repl()
} else {
Logger.warn("REPL not started. Use -r or --repl to start the REPL.")
}
}
}
fun help() {
println("""
Ghent Prolog: A Prolog interpreter in Kotlin
Options:
-s, --source <file> Specify the source file to load
-r, --repl Start the REPL (default)
-v, --verb
-h, --help Show this help message
""".trimIndent())
}

View file

@ -20,7 +20,7 @@ fi
if [ ! -f "${JAR_PATH}" ]; then
printf 'Info: JAR file not found at "%s"\n' "${JAR_PATH}"
printf 'Info: Building the project...\n'
./gradlew build
./gradlew fatJar
if [ "${?}" -ne 0 ]; then
printf 'Error: Build failed\n'
exit 1

View file

@ -2,22 +2,27 @@ package interpreter
import io.Logger
import parser.ScriptParser
import prolog.ast.Database
import prolog.Program
import prolog.ast.logic.Clause
class FileLoader {
private val parser = ScriptParser()
fun load(filePath: String): () -> Unit {
fun load(filePath: String) {
Logger.info("Loading file: $filePath")
val input = readFile(filePath)
Logger.debug("Parsing content of $filePath")
val clauses: List<Clause> = parser.parse(input)
Program.load(clauses)
val db = Database(filePath)
db.load(clauses)
Program.add(db)
db.initialize()
// TODO Pass next commands to execute
return {}
Logger.debug("Finished loading file: $filePath")
}
fun readFile(filePath: String): String {

View file

@ -47,7 +47,7 @@ open class Preprocessor {
}
}
protected open fun preprocess(term: Term): Term {
protected open fun preprocess(term: Term, nested: Boolean = false): Term {
val prepped = when (term) {
Atom("true") -> True
Structure(Atom("true"), emptyList()) -> True
@ -61,7 +61,7 @@ open class Preprocessor {
Atom("nl") -> Nl
is Structure -> {
// Preprocess the arguments first to recognize builtins
val args = term.arguments.map { preprocess(it) }
val args = term.arguments.map { preprocess(it, nested = true) }
when {
// TODO Remove hardcoding by storing the functors as constants in operators?
@ -77,7 +77,7 @@ open class Preprocessor {
term.functor == "\\+/1" -> {
Not(args[0] as Goal)
}
// Arithmetic
term.functor == "=\\=/2" && args.all { it is Expression } -> {
EvaluatesToDifferent(args[0] as Expression, args[1] as Expression)
}
@ -90,6 +90,16 @@ open class Preprocessor {
Is(args[0] as Expression, args[1] as Expression)
}
// Arithmetic
term.functor == "=/2" && args.all { it is Expression } -> {
Unify(args[0] as Expression, args[1] as Expression)
}
term.functor == "\\=/2" && args.all { it is Expression } -> {
NotUnify(args[0] as Expression, args[1] as Expression)
}
term.functor == "-/1" && args.all { it is Expression } -> {
Negate(args[0] as Expression)
}
@ -121,6 +131,7 @@ open class Preprocessor {
// Other
term.functor == "write/1" -> Write(args[0])
term.functor == "read/1" -> Read(args[0])
term.functor == "initialization/1" -> Initialization(args[0] as Goal)
else -> term
}
@ -129,9 +140,10 @@ open class Preprocessor {
else -> term
}
if (prepped != term || prepped::class != term::class) {
Logger.debug("Preprocessed term: $term -> $prepped (is ${prepped::class.simpleName})")
}
Logger.debug(
"Preprocessed term $term into $prepped (kind ${prepped::class.simpleName})",
!nested && (prepped != term || prepped::class != term::class)
)
return prepped
}

View file

@ -8,17 +8,18 @@ object Logger {
val defaultLevel: Level = Level.WARN
var level: Level = defaultLevel
private val io: IoHandler = Terminal()
private val io = Terminal()
fun log(message: String, messageLevel: Level = defaultLevel) {
if (level <= messageLevel) {
fun log(message: String, messageLevel: Level = defaultLevel, onlyIf: Boolean) {
if (level <= messageLevel && onlyIf) {
io.checkNewLine()
val text = "$messageLevel: $message\n"
io.say(text)
}
}
fun debug(message: String) = log(message, Level.DEBUG)
fun info(message: String) = log(message, Level.INFO)
fun warn(message: String) = log(message, Level.WARN)
fun error(message: String) = log(message, Level.ERROR)
fun debug(message: String, onlyIf: Boolean = true) = log(message, Level.DEBUG, onlyIf)
fun info(message: String, onlyIf: Boolean = true) = log(message, Level.INFO, onlyIf)
fun warn(message: String, onlyIf: Boolean = true) = log(message, Level.WARN, onlyIf)
fun error(message: String, onlyIf: Boolean = true) = log(message, Level.ERROR, onlyIf)
}

View file

@ -1,5 +1,6 @@
package io
import prolog.Program
import java.io.BufferedReader
import java.io.BufferedWriter
import java.io.InputStream
@ -60,4 +61,11 @@ class Terminal(
error.close()
System.exit(0)
}
fun checkNewLine() {
if (Program.storeNewLine) {
say("\n")
Program.storeNewLine = false
}
}
}

View file

@ -2,11 +2,17 @@ package parser
import com.github.h0tk3y.betterParse.grammar.Grammar
import com.github.h0tk3y.betterParse.grammar.parseToEnd
import interpreter.Preprocessor
import io.Logger
import parser.grammars.LogicGrammar
import prolog.ast.logic.Clause
class ScriptParser: Parser {
private val grammar: Grammar<List<Clause>> = LogicGrammar() as Grammar<List<Clause>>
private val preprocessor = Preprocessor()
override fun parse(input: String): List<Clause> = grammar.parseToEnd(input)
override fun parse(input: String): List<Clause> {
val raw = grammar.parseToEnd(input)
return preprocessor.preprocess(raw)
}
}

View file

@ -1,22 +1,21 @@
package parser.grammars
import com.github.h0tk3y.betterParse.combinators.oneOrMore
import com.github.h0tk3y.betterParse.combinators.or
import com.github.h0tk3y.betterParse.combinators.separated
import com.github.h0tk3y.betterParse.combinators.times
import com.github.h0tk3y.betterParse.combinators.unaryMinus
import com.github.h0tk3y.betterParse.combinators.use
import com.github.h0tk3y.betterParse.combinators.*
import com.github.h0tk3y.betterParse.parser.Parser
import prolog.ast.logic.Clause
import prolog.ast.logic.Fact
import prolog.ast.logic.Rule
import prolog.ast.terms.Atom
class LogicGrammar : TermsGrammar() {
protected val constraint: Parser<Rule> by (-neck * body) use {
Rule(Atom(""), this)
}
protected val rule: Parser<Rule> by (head * -neck * body) use { Rule(t1, t2) }
protected val fact: Parser<Fact> by head use { Fact(this) }
protected val clause: Parser<Clause> by ((rule or fact) * -dot)
protected val clause: Parser<Clause> by ((rule or constraint or fact) * -dot)
protected val clauses: Parser<List<Clause>> by oneOrMore(clause)
override val rootParser: Parser<Any> by clauses
}
}

View file

@ -1,16 +1,10 @@
package parser.grammars
import com.github.h0tk3y.betterParse.combinators.or
import com.github.h0tk3y.betterParse.combinators.separated
import com.github.h0tk3y.betterParse.combinators.times
import com.github.h0tk3y.betterParse.combinators.unaryMinus
import com.github.h0tk3y.betterParse.combinators.use
import com.github.h0tk3y.betterParse.combinators.*
import com.github.h0tk3y.betterParse.grammar.parser
import com.github.h0tk3y.betterParse.parser.Parser
import prolog.ast.arithmetic.Expression
import prolog.ast.arithmetic.Float
import prolog.ast.arithmetic.Integer
import prolog.ast.logic.LogicOperand
import prolog.ast.terms.*
open class TermsGrammar : Tokens() {
@ -37,42 +31,32 @@ open class TermsGrammar : Tokens() {
protected val float: Parser<Float> by floatToken use { Float(text.toFloat()) }
// Operators
protected val logOps: Parser<String> by (dummy
protected val ops: Parser<String> by (dummy
// Logic
or comma
or semicolon
// Arithmetic
or plus
or equals
or notEquals
) use { this.text }
protected val simpleLogicOperand: Parser<LogicOperand> by (dummy
protected val simpleOperand: Parser<Operand> by (dummy
// Logic
or compound
or atom
)
protected val logicOperand: Parser<LogicOperand> by (dummy
or parser(::logicOperator)
or simpleLogicOperand
)
protected val logicOperator: Parser<CompoundTerm> by (simpleLogicOperand * logOps * logicOperand) use {
CompoundTerm(Atom(t2), listOf(t1, t3))
}
protected val arithmeticOps: Parser<String> by (dummy
or plus
) use { this.text }
protected val simpleArithmeticOperand: Parser<Expression> by (dummy
or variable
// Arithmetic
or int
or float
)
protected val arithmeticOperand: Parser<Expression> by (dummy
or parser(::arithmeticOperator)
or simpleArithmeticOperand
) use { this as Expression }
protected val arithmeticOperator: Parser<CompoundTerm> by (simpleArithmeticOperand * arithmeticOps * arithmeticOperand) use {
protected val operand: Parser<Operand> by (dummy
or parser(::operator)
or simpleOperand
)
protected val operator: Parser<CompoundTerm> by (simpleOperand * ops * operand) use {
CompoundTerm(Atom(t2), listOf(t1, t3))
}
protected val operator: Parser<CompoundTerm> by (dummy
or logicOperator
or arithmeticOperator
)
// Parts
protected val head: Parser<Head> by (dummy
or compound
@ -81,6 +65,7 @@ open class TermsGrammar : Tokens() {
protected val body: Parser<Body> by (dummy
or operator
or head
or variable
) use { this as Body }
protected val term: Parser<Term> by (dummy

View file

@ -22,6 +22,8 @@ abstract class Tokens : Grammar<Any>() {
protected val rightParenthesis: Token by literalToken(")")
protected val comma: Token by literalToken(",")
protected val semicolon: Token by literalToken(";")
protected val equals: Token by literalToken("=")
protected val notEquals: Token by literalToken("\\=")
protected val plus: Token by literalToken("+")
protected val dot by literalToken(".")

View file

@ -1,32 +1,25 @@
package prolog
import io.Logger
import prolog.ast.Database
import prolog.ast.logic.Clause
import prolog.ast.logic.Predicate
import prolog.ast.logic.Resolvent
import prolog.ast.terms.Functor
import prolog.ast.terms.Goal
typealias Database = Program
/**
* Prolog Program or database.
* Object to handle execution
*
* This object is a singleton that manages a list of databases.
*/
object Program: Resolvent {
var predicates: Map<Functor, Predicate> = emptyMap()
object Program : Resolvent {
private val internalDb = Database("")
private val databases: MutableList<Database> = mutableListOf(internalDb)
init {
Logger.debug("Initializing ${this::class.java.simpleName}")
setup()
Logger.debug("Initialization of ${this::class.java.simpleName} complete")
}
var storeNewLine: Boolean = false
var variableRenamingStart: Int = 0
private fun setup() {
Logger.debug("Setting up ${this::class.java.simpleName}")
// Initialize the program with built-in predicates
load(listOf(
))
fun add(database: Database) {
databases.add(database)
}
/**
@ -35,51 +28,24 @@ object Program: Resolvent {
*/
fun query(goal: Goal): Answers = solve(goal, emptyMap())
override fun solve(goal: Goal, subs: Substitutions): Answers {
override fun solve(goal: Goal, subs: Substitutions): Answers = sequence {
Logger.debug("Solving goal $goal")
val functor = goal.functor
// If the predicate does not exist, return false
val predicate = predicates[functor] ?: return emptySequence()
// If the predicate exists, evaluate the goal against it
return predicate.solve(goal, subs)
}
/**
* Loads a list of clauses into the program.
*/
fun load(clauses: List<Clause>) {
for (clause in clauses) {
val functor = clause.functor
val predicate = predicates[functor]
if (predicate != null) {
// If the predicate already exists, add the clause to it
predicate.add(clause)
} else {
// If the predicate does not exist, create a new one
predicates += Pair(functor, Predicate(listOf(clause)))
}
Logger.debug("Loaded clause $clause into predicate $functor")
for (database in databases) {
yieldAll(database.solve(goal, subs))
}
}
fun load(predicate: Predicate) {
val functor = predicate.functor
val existingPredicate = predicates[functor]
if (existingPredicate != null) {
// If the predicate already exists, add the clauses to it
existingPredicate.addAll(predicate.clauses)
} else {
// If the predicate does not exist, create a new one
predicates += Pair(functor, predicate)
}
}
fun load(clauses: List<Clause>) = internalDb.load(clauses)
fun clear() {
Logger.debug("Clearing ${this::class.java.simpleName}")
predicates = emptyMap()
setup()
databases.forEach { it.clear() }
}
fun clear(filePath: String) {
val correspondingDBs = databases.filter { it.sourceFile == filePath }
require(correspondingDBs.isNotEmpty()) { "No database found for file: $filePath" }
correspondingDBs.forEach { it.clear() }
}
}

View file

@ -4,7 +4,7 @@ import prolog.ast.terms.Term
abstract class Substitution(val from: Term, val to: Term) {
val mapped: Pair<Term, Term>? = if (from != to) from to to else null
override fun toString(): String = "$from -> $to"
override fun toString(): String = "$from |-> $to"
}
typealias Substitutions = Map<Term, Term>
typealias Answer = Result<Substitutions>

View file

@ -0,0 +1,76 @@
package prolog.ast
import io.Logger
import prolog.Program
import prolog.Answers
import prolog.Substitutions
import prolog.ast.logic.Clause
import prolog.ast.logic.Predicate
import prolog.ast.logic.Resolvent
import prolog.ast.terms.Functor
import prolog.ast.terms.Goal
/**
* Prolog Program or Database
*/
class Database(val sourceFile: String): Resolvent {
private var predicates: Map<Functor, Predicate> = emptyMap()
fun initialize() {
Logger.info("Initializing database from $sourceFile")
if (predicates.contains("/_")) {
Logger.debug("Loading clauses from /_ predicate")
predicates["/_"]?.clauses?.forEach {
Logger.debug("Loading clause $it")
val goal = it.body as Goal
goal.satisfy(emptyMap()).toList()
}
}
}
override fun solve(goal: Goal, subs: Substitutions): Answers {
val functor = goal.functor
// If the predicate does not exist, return false
val predicate = predicates[functor] ?: return emptySequence()
// If the predicate exists, evaluate the goal against it
return predicate.solve(goal, subs)
}
/**
* Loads a list of clauses into the program.
*/
fun load(clauses: List<Clause>) {
for (clause in clauses) {
val functor = clause.functor
val predicate = predicates[functor]
if (predicate != null) {
// If the predicate already exists, add the clause to it
predicate.add(clause)
} else {
// If the predicate does not exist, create a new one
predicates += Pair(functor, Predicate(listOf(clause)))
}
Logger.debug("Loaded clause $clause into predicate $functor")
}
}
fun load(predicate: Predicate) {
val functor = predicate.functor
val existingPredicate = predicates[functor]
if (existingPredicate != null) {
// If the predicate already exists, add the clauses to it
existingPredicate.addAll(predicate.clauses)
} else {
// If the predicate does not exist, create a new one
predicates += Pair(functor, predicate)
}
}
fun clear() {
Logger.debug("Clearing ${this::class.java.simpleName}")
predicates = emptyMap()
}
}

View file

@ -31,4 +31,15 @@ class Float(override val value: kotlin.Float): Number {
is Integer -> Float(value * other.value.toFloat())
else -> throw IllegalArgumentException("Cannot multiply $this and $other")
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Float) return false
if (value != other.value) return false
return true
}
override fun hashCode(): Int {
return super.hashCode()
}
}

View file

@ -1,13 +1,13 @@
package prolog.ast.logic
import prolog.Answers
import prolog.Program
import prolog.Substitutions
import prolog.ast.terms.Body
import prolog.ast.terms.Functor
import prolog.ast.terms.Goal
import prolog.ast.terms.Head
import prolog.ast.terms.*
import prolog.builtins.True
import prolog.flags.AppliedCut
import prolog.logic.applySubstitution
import prolog.logic.numbervars
import prolog.logic.unifyLazy
/**
@ -21,23 +21,39 @@ import prolog.logic.unifyLazy
abstract class Clause(val head: Head, val body: Body) : Resolvent {
val functor: Functor = head.functor
override fun solve (goal: Goal, subs: Substitutions): Answers = sequence {
override fun solve(goal: Goal, subs: Substitutions): Answers = sequence {
// If the clause is a rule, unify the goal with the head and then try to prove the body.
// Only if the body can be proven, the substitutions should be returned.
// Do this in a lazy way.
unifyLazy(goal, head, subs).forEach { headAnswer ->
headAnswer.map { newHeadSubs ->
// Since we are only interested in substitutions in the goal (as opposed to the head of this clause),
// we can use variable renaming and filter out the substitutions that are not in the goal.
val (end, renamed: Substitutions) = numbervars(head, Program.variableRenamingStart, subs)
val reverse = renamed.entries.associate { (a, b) -> b to a }
Program.variableRenamingStart = end
var newSubs: Substitutions = subs + renamed
unifyLazy(goal, head, newSubs).forEach { headAnswer ->
headAnswer.map { headSubs ->
// If the body can be proven, yield the (combined) substitutions
body.satisfy(subs + newHeadSubs).forEach { bodyAnswer ->
newSubs = subs + renamed + headSubs
body.satisfy(newSubs).forEach { bodyAnswer ->
bodyAnswer.fold(
onSuccess = { newBodySubs ->
yield(Result.success(newHeadSubs + newBodySubs))
onSuccess = { bodySubs ->
var result = (headSubs + bodySubs)
.mapKeys { reverse[it.key] ?: it.key }
.mapValues { reverse[it.value] ?: it.value }
result = result.map { it.key to applySubstitution(it.value, result) }
.toMap()
.filterNot { it.key in renamed.keys }
yield(Result.success(result))
},
onFailure = { error ->
if (error is AppliedCut) {
// Find single solution and return immediately
if (error.subs != null) {
yield(Result.failure(AppliedCut(newHeadSubs + error.subs)))
yield(Result.failure(AppliedCut(headSubs + error.subs)))
} else {
yield(Result.failure(AppliedCut()))
}
@ -52,10 +68,5 @@ abstract class Clause(val head: Head, val body: Body) : Resolvent {
}
}
override fun toString(): String {
return when {
body is True -> head.toString()
else -> "$head :- $body"
}
}
}
override fun toString(): String = if (body is True) head.toString() else "$head :- $body"
}

View file

@ -51,6 +51,7 @@ class Predicate : Resolvent {
override fun solve(goal: Goal, subs: Substitutions): Answers = sequence {
require(goal.functor == functor) { "Goal functor does not match predicate functor" }
// Try to unify the goal with the clause
// If the unification is successful, yield the substitutions
clauses.forEach { clause ->

View file

@ -1,10 +1,11 @@
package prolog.ast.terms
import prolog.Answers
import prolog.Substitutions
import prolog.ast.arithmetic.Expression
import prolog.ast.arithmetic.Simplification
data class Variable(val name: String) : Term, Expression {
data class Variable(val name: String) : Term, Body, Expression {
override fun simplify(subs: Substitutions): Simplification {
// If the variable is bound, return the value of the binding
// If the variable is not bound, return the variable itself
@ -16,5 +17,15 @@ data class Variable(val name: String) : Term, Expression {
return Simplification(this, result)
}
override fun satisfy(subs: Substitutions): Answers {
// If the variable is bound, satisfy the bound term
if (this in subs) {
val boundTerm = subs[this]!! as Body
return boundTerm.satisfy(subs)
}
return sequenceOf(Result.failure(IllegalArgumentException("Unbound variable: $this")))
}
override fun toString(): String = name
}

View file

@ -4,6 +4,7 @@ import io.Logger
import io.Terminal
import parser.ReplParser
import prolog.Answers
import prolog.Program
import prolog.Substitutions
import prolog.ast.logic.Satisfiable
import prolog.ast.terms.Atom
@ -21,6 +22,8 @@ class Write(private val term: Term) : Operator(Atom("write"), null, term), Satis
Terminal().say(t.toString())
Program.storeNewLine = true
return sequenceOf(Result.success(emptyMap()))
}
}
@ -31,6 +34,7 @@ class Write(private val term: Term) : Operator(Atom("write"), null, term), Satis
object Nl : Atom("nl"), Satisfiable {
override fun satisfy(subs: Substitutions): Answers {
Terminal().say("\n")
Program.storeNewLine = false
return sequenceOf(Result.success(emptyMap()))
}
}

View file

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

View file

@ -26,6 +26,11 @@ class Unify(private val term1: Term, private val term2: Term): Operator(Atom("="
}
}
class NotUnify(term1: Term, term2: Term) : Operator(Atom("\\="), term1, term2) {
private val not = Not(Unify(term1, term2))
override fun satisfy(subs: Substitutions): Answers = not.satisfy(subs)
}
class Equivalent(private val term1: Term, private val term2: Term) : Operator(Atom("=="), term1, term2) {
override fun satisfy(subs: Substitutions): Answers = sequence {
val t1 = applySubstitution(term1, subs)

View file

@ -1,7 +1,10 @@
package prolog.logic
import prolog.Substitutions
import prolog.ast.terms.Atom
import prolog.ast.terms.Structure
import prolog.ast.terms.Term
import prolog.ast.terms.Variable
/**
* True when Term is a term with functor Name/Arity. If Term is a variable it is unified with a new term whose
@ -20,3 +23,53 @@ fun functor(term: Term, name: Atom, arity: Int): Boolean {
// TODO Implement
return true
}
/**
* Unify the free variables in Term with a term $VAR(N), where N is the number of the variable.
* Counting starts at Start.
* End is unified with the number that should be given to the next variable.
*
* Source: [SWI-Prolog Predicate numbervars/3](https://www.swi-prolog.org/pldoc/man?predicate=numbervars/3)
*
* @return Pair of the next number and only the new substitutions of variables to $VAR(N)
*/
fun numbervars(
term: Term,
start: Int = 0,
subs: Substitutions = emptyMap(),
sessionSubs: Substitutions = emptyMap()
): Pair<Int, Substitutions> {
when {
variable(term, subs) -> {
// All instances of the same variable are unified with the same term
if (term in sessionSubs) {
return Pair(start, emptyMap())
}
val from = term as Variable
var suggestedName = "${from.name}($start)"
// If the suggested name is already in use, find a new one
while ((subs + sessionSubs).filter { (it.key as Variable).name == suggestedName }.isNotEmpty()) {
val randomInfix = ((0..9) + ('a'..'z') + ('A'..'Z')).random()
suggestedName = "${from.name}_${randomInfix}_($start)"
}
return Pair(start + 1, mapOf(from to Variable(suggestedName)))
}
compound(term, subs) -> {
val from = term as Structure
var n = start
val s: MutableMap<Term, Term> = sessionSubs.toMutableMap()
from.arguments.forEach { arg ->
val (newN, newSubs) = numbervars(arg, n, subs, s)
n = newN
s += newSubs
}
return Pair(n, s)
}
else -> {
return Pair(start, emptyMap())
}
}
}

View file

@ -36,7 +36,7 @@ fun applySubstitution(expr: Expression, subs: Substitutions): Expression = when
}
// Check if a variable occurs in a term
private fun occurs(variable: Variable, term: Term, subs: Substitutions): Boolean = when {
fun occurs(variable: Variable, term: Term, subs: Substitutions): Boolean = when {
variable(term, subs) -> term == variable
atomic(term, subs) -> false
compound(term, subs) -> {
@ -53,18 +53,18 @@ fun unifyLazy(term1: Term, term2: Term, subs: Substitutions): Answers = sequence
val t2 = applySubstitution(term2, subs)
when {
equivalent(t1, t2, subs) -> yield(Result.success(subs))
equivalent(t1, t2, subs) -> yield(Result.success(emptyMap()))
variable(t1, subs) -> {
val variable = t1 as Variable
if (!occurs(variable, t2, subs)) {
yield(Result.success(subs + (variable to t2)))
yield(Result.success(mapOf(term1 to t2)))
}
}
variable(t2, subs) -> {
val variable = t2 as Variable
if (!occurs(variable, t1, subs)) {
yield(Result.success(subs + (variable to t1)))
yield(Result.success(mapOf(term2 to t1)))
}
}

View file

@ -6,14 +6,15 @@ import io.Terminal
import parser.ReplParser
import prolog.Answer
import prolog.Answers
import prolog.Program
class Repl {
private val io = Terminal()
private val parser = ReplParser()
private val preprocessor = Preprocessor()
fun start() {
io.say("Prolog REPL. Type '^D' to quit.\n")
init {
welcome()
while (true) {
try {
printAnswers(query())
@ -23,15 +24,19 @@ class Repl {
}
}
fun query(): Answers {
private fun welcome() {
io.checkNewLine()
io.say("Prolog REPL. Type '^D' to quit.\n")
}
private fun query(): Answers {
val queryString = io.prompt("?-", { "| " })
val simpleQuery = parser.parse(queryString)
val query = preprocessor.preprocess(simpleQuery)
Logger.debug("Satisfying query: $query")
return query.satisfy(emptyMap())
}
fun printAnswers(answers: Answers) {
private fun printAnswers(answers: Answers) {
val knownCommands = setOf(";", "a", ".", "h")
val iterator = answers.iterator()
@ -68,7 +73,7 @@ class Repl {
io.say("\n")
}
fun help(): String {
private fun help(): String {
io.say("Commands:\n")
io.say(" ; find next solution\n")
io.say(" a abort\n")
@ -77,12 +82,13 @@ class Repl {
return ""
}
fun prettyPrint(result: Answer): String {
private fun prettyPrint(result: Answer): String {
result.fold(
onSuccess = {
val subs = result.getOrNull()!!
if (subs.isEmpty()) {
return "true."
io.checkNewLine()
return "true.\n"
}
return subs.entries.joinToString(",\n") { "${it.key} = ${it.value}" }
},