Checkpoint
This commit is contained in:
parent
43b364044e
commit
9db1c66781
34 changed files with 746 additions and 194 deletions
16
src/Main.kt
16
src/Main.kt
|
@ -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())
|
||||
}
|
||||
|
|
2
src/gpl
2
src/gpl
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(".")
|
||||
|
||||
|
|
|
@ -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() }
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
76
src/prolog/ast/Database.kt
Normal file
76
src/prolog/ast/Database.kt
Normal 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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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 ->
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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}" }
|
||||
},
|
||||
|
|
Reference in a new issue